@@ -36,7 +36,7 @@ class Program | |||
// you must set the MessageCacheSize. You may adjust the number as needed. | |||
//MessageCacheSize = 50, | |||
// If your platform doesn't have native websockets, | |||
// If your platform doesn't have native WebSockets, | |||
// add Discord.Net.Providers.WS4Net from NuGet, | |||
// add the `using` at the top, and uncomment this line: | |||
//WebSocketProvider = WS4NetProvider.Instance | |||
@@ -192,7 +192,7 @@ namespace Discord.API | |||
internal override async Task DisconnectInternalAsync() | |||
{ | |||
if (_webSocketClient == null) | |||
throw new NotSupportedException("This client is not configured with websocket support."); | |||
throw new NotSupportedException("This client is not configured with WebSocket support."); | |||
if (ConnectionState == ConnectionState.Disconnected) return; | |||
ConnectionState = ConnectionState.Disconnecting; | |||
@@ -14,7 +14,7 @@ namespace Discord.Rpc | |||
/// <summary> Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. </summary> | |||
public int ConnectionTimeout { get; set; } = 30000; | |||
/// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | |||
/// <summary> Gets or sets the provider used to generate new WebSocket connections. </summary> | |||
public WebSocketProvider WebSocketProvider { get; set; } | |||
public DiscordRpcConfig() | |||
@@ -24,7 +24,7 @@ namespace Discord.Rpc | |||
#else | |||
WebSocketProvider = () => | |||
{ | |||
throw new InvalidOperationException("The default websocket provider is not supported on this platform.\n" + | |||
throw new InvalidOperationException("The default WebSocket provider is not supported on this platform.\n" + | |||
"You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); | |||
}; | |||
#endif | |||
@@ -117,6 +117,7 @@ namespace Discord.Commands.Builders | |||
return this; | |||
} | |||
/// <exception cref="InvalidOperationException">Only the last parameter in a command may have the Remainder or Multiple flag.</exception> | |||
internal CommandInfo Build(ModuleInfo info, CommandService service) | |||
{ | |||
//Default name to primary alias | |||
@@ -40,17 +40,17 @@ namespace Discord.Commands | |||
internal readonly LogManager _logManager; | |||
/// <summary> | |||
/// Represents all modules loaded within <see cref="CommandService" /> . | |||
/// Represents all modules loaded within <see cref="CommandService" />. | |||
/// </summary> | |||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | |||
/// <summary> | |||
/// Represents all commands loaded within <see cref="CommandService" /> . | |||
/// Represents all commands loaded within <see cref="CommandService" />. | |||
/// </summary> | |||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | |||
/// <summary> | |||
/// Represents all <see cref="TypeReader" /> loaded within <see cref="CommandService" /> . | |||
/// Represents all <see cref="TypeReader" /> loaded within <see cref="CommandService" />. | |||
/// </summary> | |||
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); | |||
@@ -122,12 +122,12 @@ namespace Discord.Commands | |||
} | |||
/// <summary> | |||
/// Add a command module from a <see cref="Type" /> . | |||
/// Add a command module from a <see cref="Type" />. | |||
/// </summary> | |||
/// <typeparam name="T">The type of module.</typeparam> | |||
/// <param name="services"> | |||
/// The <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass | |||
/// <see langword="null" /> . | |||
/// <see langword="null" />. | |||
/// </param> | |||
/// <returns> | |||
/// A built module. | |||
@@ -135,12 +135,12 @@ namespace Discord.Commands | |||
public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services); | |||
/// <summary> | |||
/// Adds a command module from a <see cref="Type" /> . | |||
/// Adds a command module from a <see cref="Type" />. | |||
/// </summary> | |||
/// <param name="type">The type of module.</param> | |||
/// <param name="services"> | |||
/// The <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass | |||
/// <see langword="null" /> . | |||
/// <see langword="null" />. | |||
/// </param> | |||
/// <returns> | |||
/// A built module. | |||
@@ -174,12 +174,12 @@ namespace Discord.Commands | |||
} | |||
} | |||
/// <summary> | |||
/// Add command modules from an <see cref="Assembly" /> . | |||
/// Add command modules from an <see cref="Assembly" />. | |||
/// </summary> | |||
/// <param name="assembly">The <see cref="Assembly" /> containing command modules.</param> | |||
/// <param name="services"> | |||
/// An <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass | |||
/// <see langword="null" /> . | |||
/// <see langword="null" />. | |||
/// </param> | |||
/// <returns> | |||
/// A collection of built modules. | |||
@@ -16,7 +16,7 @@ namespace Discord.Commands | |||
/// </summary> | |||
/// <remarks> | |||
/// This object contains the information of a command. This can include the module of the command, various | |||
/// descriptions regarding the command, and its <see cref="RunMode" /> . | |||
/// descriptions regarding the command, and its <see cref="RunMode" />. | |||
/// </remarks> | |||
[DebuggerDisplay("{Name,nq}")] | |||
public class CommandInfo | |||
@@ -23,6 +23,7 @@ namespace Discord.Commands | |||
_commands = ImmutableArray.Create<CommandInfo>(); | |||
} | |||
/// <exception cref="InvalidOperationException">Cannot add commands to the root node.</exception> | |||
public void AddCommand(CommandService service, string text, int index, CommandInfo command) | |||
{ | |||
int nextSegment = NextSegment(text, index, service._separatorChar); | |||
@@ -17,10 +17,12 @@ namespace Discord.Commands | |||
private readonly TryParseDelegate<T> _tryParse; | |||
private readonly float _score; | |||
/// <exception cref="ArgumentOutOfRangeException"><typeparamref name="T"/> must be within the range [0, 1].</exception> | |||
public PrimitiveTypeReader() | |||
: this(PrimitiveParsers.Get<T>(), 1) | |||
{ } | |||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="score"/> must be within the range [0, 1].</exception> | |||
public PrimitiveTypeReader(TryParseDelegate<T> tryParse, float score) | |||
{ | |||
if (score < 0 || score > 1) | |||
@@ -15,7 +15,7 @@ namespace Discord.Audio | |||
/// <summary> Gets the current connection state of this client. </summary> | |||
ConnectionState ConnectionState { get; } | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice websocket server. </summary> | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice WebSocket server. </summary> | |||
int Latency { get; } | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice UDP server. </summary> | |||
int UdpLatency { get; } | |||
@@ -13,7 +13,7 @@ namespace Discord | |||
public static string GetApplicationIconUrl(ulong appId, string iconId) | |||
=> iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; | |||
/// <summary> | |||
/// Returns the user avatar URL based on the <paramref name="size"/> and <see cref="ImageFormat" /> . | |||
/// Returns the user avatar URL based on the <paramref name="size"/> and <see cref="ImageFormat" />. | |||
/// </summary> | |||
public static string GetUserAvatarUrl(ulong userId, string avatarId, ushort size, ImageFormat format) | |||
{ | |||
@@ -52,7 +52,7 @@ namespace Discord | |||
=> $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}"; | |||
/// <summary> | |||
/// Returns the rich presence asset URL based on the asset ID and <see cref="ImageFormat" /> . | |||
/// Returns the rich presence asset URL based on the asset ID and <see cref="ImageFormat" />. | |||
/// </summary> | |||
public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format) | |||
{ | |||
@@ -1,7 +1,7 @@ | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> | |||
/// Represents the context of a command. This may include the client, guild, channel, user, and message. | |||
/// Represents a context of a command. This may include the client, guild, channel, user, and message. | |||
/// </summary> | |||
public interface ICommandContext | |||
{ | |||
@@ -18,7 +18,7 @@ namespace Discord | |||
/// Creates a <see cref="Game"/> with the provided <paramref name="name"/> and <see cref="ActivityType"/>. | |||
/// </summary> | |||
/// <param name="name">The name of the game.</param> | |||
/// <param name="type">The type of activity. Default is <see cref="Discord.ActivityType.Playing" /> .</param> | |||
/// <param name="type">The type of activity. Default is <see cref="Discord.ActivityType.Playing" />.</param> | |||
public Game(string name, ActivityType type = ActivityType.Playing) | |||
{ | |||
Name = name; | |||
@@ -1,7 +1,7 @@ | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Properties that are used to reorder an <see cref="IGuildChannel" /> . | |||
/// Properties that are used to reorder an <see cref="IGuildChannel" />. | |||
/// </summary> | |||
public class ReorderChannelProperties | |||
{ | |||
@@ -8,11 +8,11 @@ namespace Discord | |||
public class EmoteProperties | |||
{ | |||
/// <summary> | |||
/// Gets or sets the name of the <see cref="Emote" /> . | |||
/// Gets or sets the name of the <see cref="Emote" />. | |||
/// </summary> | |||
public Optional<string> Name { get; set; } | |||
/// <summary> | |||
/// Gets or sets the roles that can access this <see cref="Emote" /> . | |||
/// Gets or sets the roles that can access this <see cref="Emote" />. | |||
/// </summary> | |||
public Optional<IEnumerable<IRole>> Roles { get; set; } | |||
} | |||
@@ -6,7 +6,7 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a generic guild object. | |||
/// Represents a generic guild/server. | |||
/// </summary> | |||
public interface IGuild : IDeletable, ISnowflakeEntity | |||
{ | |||
@@ -23,7 +23,7 @@ namespace Discord | |||
/// Determines if this guild is embeddable (i.e. can use widget). | |||
/// </summary> | |||
/// <returns> | |||
/// Returns <see langword="true"/> if this guild can be embedded via widgets. | |||
/// <see langword="true"/> if this guild can be embedded via widgets; otherwise <see langword="false"/>. | |||
/// </returns> | |||
bool IsEmbeddable { get; } | |||
/// <summary> | |||
@@ -91,58 +91,91 @@ namespace Discord | |||
/// <summary> | |||
/// Gets the <see cref="IAudioClient" /> currently associated with this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// <see cref="IAudioClient" /> currently associated with this guild. | |||
/// </returns> | |||
IAudioClient AudioClient { get; } | |||
/// <summary> | |||
/// Gets the built-in role containing all users in this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// Built-in role that represents an @everyone role in this guild. | |||
/// </returns> | |||
IRole EveryoneRole { get; } | |||
/// <summary> | |||
/// Gets a collection of all custom emotes for this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// A collection of all custom emotes for this guild. | |||
/// </returns> | |||
IReadOnlyCollection<GuildEmote> Emotes { get; } | |||
/// <summary> | |||
/// Gets a collection of all extra features added to this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// A collection of enabled features in this guild. | |||
/// </returns> | |||
IReadOnlyCollection<string> Features { get; } | |||
/// <summary> | |||
/// Gets a collection of all roles in this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// A collection of roles found within this guild. | |||
/// </returns> | |||
IReadOnlyCollection<IRole> Roles { get; } | |||
/// <summary> | |||
/// Modifies this guild. | |||
/// </summary> | |||
/// <param name="func">The properties to modify the guild with.</param> | |||
/// <param name="func">The delegate containing the properties to modify the guild with.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies this guild's embed channel. | |||
/// </summary> | |||
/// <param name="func">The properties to modify the guild widget with.</param> | |||
/// <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> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Bulk modifies the order of channels in this guild. | |||
/// </summary> | |||
/// <param name="args">The properties to modify the channel positions with.</param> | |||
/// <param name="args">The properties used to modify the channel positions with.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null); | |||
/// <summary> | |||
/// Bulk modifies the order of roles in this guild. | |||
/// </summary> | |||
/// <param name="args">The properties to modify the role positions with.</param> | |||
/// <param name="args">The properties used to modify the role positions with.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null); | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
/// <summary> | |||
/// Leaves this guild. If you are the owner, use <see cref="IDeletable.DeleteAsync" /> instead. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task LeaveAsync(RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all users banned on this guild. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing a collection of banned users with reasons. | |||
/// </returns> | |||
Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); | |||
/// <summary> | |||
/// Bans the provided user from this guild and optionally prunes their recent messages. | |||
@@ -154,6 +187,9 @@ namespace Discord | |||
/// <param name="reason">The reason of the ban to be written in the audit log.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <exception cref="ArgumentException"><paramref name="pruneDays" /> is not between 0 to 7.</exception> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); | |||
/// <summary> | |||
/// Bans the provided user ID from this guild and optionally prunes their recent messages. | |||
@@ -165,14 +201,27 @@ namespace Discord | |||
/// <param name="reason">The reason of the ban to be written in the audit log.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <exception cref="ArgumentException"><paramref name="pruneDays" /> is not between 0 to 7.</exception> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); | |||
/// <summary> | |||
/// Unbans the provided user if they are currently banned. | |||
/// </summary> | |||
/// <param name="user">The user to be unbanned.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task RemoveBanAsync(IUser user, RequestOptions options = null); | |||
/// <summary> | |||
/// Unbans the provided user ID if it is currently banned. | |||
/// </summary> | |||
/// <param name="userId">The snowflake ID of the user to be unbanned.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task RemoveBanAsync(ulong userId, RequestOptions options = null); | |||
/// <summary> | |||
@@ -182,15 +231,22 @@ namespace Discord | |||
/// 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> | |||
/// An awaitable <see cref="Task"/> containing a collection of generic channels found within this guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
/// Gets the channel in this guild with the provided ID. | |||
/// </summary> | |||
/// <param name="id">The channel ID.</param> | |||
/// <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> | |||
/// An awaitable <see cref="Task"/> containing the generic channel with the specified ID, or | |||
/// <see langword="null"/> if none is found. | |||
/// </returns> | |||
Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all text channels in this guild. | |||
@@ -199,6 +255,9 @@ namespace Discord | |||
/// 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> | |||
/// An awaitable <see cref="Task"/> containing a collection of text channels found within this guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a text channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
@@ -208,6 +267,10 @@ namespace Discord | |||
/// 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> | |||
/// An awaitable <see cref="Task"/> containing the text channel with the specified ID, or | |||
/// <see langword="null"/> if none is found. | |||
/// </returns> | |||
Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all voice channels in this guild. | |||
@@ -216,6 +279,9 @@ namespace Discord | |||
/// 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> | |||
/// An awaitable <see cref="Task"/> containing a collection of voice channels found within this guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all category channels in this guild. | |||
@@ -224,68 +290,97 @@ namespace Discord | |||
/// 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> | |||
/// An awaitable <see cref="Task"/> containing a collection of category channels found within this guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the voice channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
/// Gets the voice channel in this guild with the provided ID. | |||
/// </summary> | |||
/// <param name="id">The text channel ID.</param> | |||
/// <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> | |||
/// An awaitable <see cref="Task"/> containing the voice channel with the specified ID, or | |||
/// <see langword="null"/> if none is found. | |||
/// </returns> | |||
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the voice AFK channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
/// Gets the AFK voice channel 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> | |||
/// An awaitable <see cref="Task"/> containing the AFK voice channel set within this guild, or | |||
/// <see langword="null"/> if none is set. | |||
/// </returns> | |||
Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the default system text channel in this guild with the provided ID, or <see langword="null" /> if | |||
/// none is set. | |||
/// Gets the default system text channel in this guild with the provided ID. | |||
/// </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> | |||
/// An awaitable <see cref="Task"/> containing the system channel within this guild, or | |||
/// <see langword="null" /> if none is set. | |||
/// </returns> | |||
Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the top viewable text channel in this guild with the provided ID, or <see langword="null" /> if not | |||
/// found. | |||
/// Gets the top viewable text channel in this guild with the provided ID. | |||
/// </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> | |||
/// An awaitable <see cref="Task"/> containing the first viewable text channel in this guild, or | |||
/// <see langword="null"/> if none is found. | |||
/// </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, or | |||
/// <see langword="null" /> if none is set. | |||
/// 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> | |||
/// An awaitable <see cref="Task"/> containing the embed channel set within the server's widget settings, or | |||
/// <see langword="null"/> if none is set. | |||
/// </returns> | |||
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Creates a new text channel. | |||
/// </summary> | |||
/// <param name="name">The new name for the text channel.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the newly created text channel. | |||
/// </returns> | |||
Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null); | |||
/// <summary> | |||
/// Creates a new voice channel. | |||
/// </summary> | |||
/// <param name="name">The new name for the voice channel.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the newly created voice channel. | |||
/// </returns> | |||
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null); | |||
/// <summary> | |||
/// Creates a new channel category. | |||
/// </summary> | |||
/// <param name="name">The new name for the category.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the newly created category channel. | |||
/// </returns> | |||
Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null); | |||
Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null); | |||
@@ -309,13 +404,24 @@ namespace Discord | |||
/// <param name="color">The color of the role.</param> | |||
/// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the newly crated role. | |||
/// </returns> | |||
Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all users in this guild. | |||
/// </summary> | |||
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
/// <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> | |||
/// An awaitable <see cref="Task"/> containing a collection of users found within this guild. | |||
/// </returns> | |||
/// <remarks> | |||
/// This may return an incomplete list on the WebSocket implementation because Discord only sends offline | |||
/// users on large guilds. | |||
/// </remarks> | |||
Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the user in this guild with the provided ID, or <see langword="null"/> if not found. | |||
@@ -323,22 +429,34 @@ namespace Discord | |||
/// <param name="id">The user ID.</param> | |||
/// <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> | |||
/// An awaitable <see cref="Task"/> containing the guild user with the specified ID, otherwise <see langword="null"/>. | |||
/// </returns> | |||
Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the current user for 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> | |||
/// An awaitable <see cref="Task"/> containing the currently logged-in user within this guild. | |||
/// </returns> | |||
Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the owner of 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> | |||
/// An awaitable <see cref="Task"/> containing the owner of this guild. | |||
/// </returns> | |||
Task<IGuildUser> GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Downloads all users for this guild if the current list is incomplete. | |||
/// </summary> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task DownloadUsersAsync(); | |||
/// <summary> | |||
/// Removes all users from this guild if they have not logged on in a provided number of | |||
@@ -349,7 +467,7 @@ namespace Discord | |||
/// <param name="simulate">Whether this prune action is a simulation.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// The number of users removed from this guild. | |||
/// An awaitable <see cref="Task"/> containing the number of users to be or has been removed from this guild. | |||
/// </returns> | |||
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); | |||
@@ -358,11 +476,17 @@ namespace Discord | |||
/// </summary> | |||
/// <param name="id">The webhook ID.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the webhook with the specified ID, otherwise <see langword="null"/>. | |||
/// </returns> | |||
Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all webhook from this guild. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing a collection of webhooks found within the guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | |||
/// <summary> | |||
@@ -370,6 +494,9 @@ namespace Discord | |||
/// </summary> | |||
/// <param name="id">The guild emote ID.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the emote found with the specified ID, or <see langword="null"/> if not found. | |||
/// </returns> | |||
Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null); | |||
/// <summary> | |||
/// Creates a new <see cref="GuildEmote"/> in this guild. | |||
@@ -378,20 +505,29 @@ namespace Discord | |||
/// <param name="image">The image of the new emote.</param> | |||
/// <param name="roles">The roles to limit the emote usage to.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the created emote. | |||
/// </returns> | |||
Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies an existing <see cref="GuildEmote"/> in this guild. | |||
/// </summary> | |||
/// <param name="emote">The emote to be modified.</param> | |||
/// <param name="func">The properties to modify the emote with.</param> | |||
/// <param name="func">The delegate containing the properties to modify the emote with.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the newly modified emote. | |||
/// </returns> | |||
Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Deletes an existing <see cref="GuildEmote"/> from this guild. | |||
/// </summary> | |||
/// <param name="emote">The emote to delete.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); | |||
} | |||
} |
@@ -3,7 +3,7 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents whether the object is updatable or not. | |||
/// Defines whether the object is updateable or not. | |||
/// </summary> | |||
public interface IUpdateable | |||
{ | |||
@@ -33,9 +33,9 @@ namespace Discord | |||
/// <param name="path">The path to the file.</param> | |||
/// <exception cref="ArgumentException"> | |||
/// <paramref name="path" /> is a zero-length string, contains only white space, or contains one or more invalid | |||
/// characters as defined by <see cref="Path.GetInvalidPathChars" /> . | |||
/// characters as defined by <see cref="Path.GetInvalidPathChars" />. | |||
/// </exception> | |||
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <see langword="null" /> .</exception> | |||
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <see langword="null" />.</exception> | |||
/// <exception cref="PathTooLongException"> | |||
/// The specified path, file name, or both exceed the system-defined maximum length. For example, on | |||
/// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 | |||
@@ -172,7 +172,7 @@ namespace Discord | |||
} | |||
/// <summary> | |||
/// Sets the title of an <see cref="Embed" /> . | |||
/// Sets the title of an <see cref="Embed" />. | |||
/// </summary> | |||
/// <param name="title">The title to be set.</param> | |||
/// <returns> | |||
@@ -3,7 +3,7 @@ using System.Diagnostics; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a field for an <see cref="Embed" /> . | |||
/// Represents a field for an <see cref="Embed" />. | |||
/// </summary> | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedField | |||
@@ -3,7 +3,7 @@ using System.Diagnostics; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// A video featured in an <see cref="Embed" /> . | |||
/// A video featured in an <see cref="Embed" />. | |||
/// </summary> | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedVideo | |||
@@ -4,7 +4,7 @@ using System.Collections.Generic; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a Discord message object. | |||
/// Represents a message object. | |||
/// </summary> | |||
public interface IMessage : ISnowflakeEntity, IDeletable | |||
{ | |||
@@ -4,20 +4,21 @@ namespace Discord | |||
/// Properties that are used to modify an <see cref="IUserMessage" /> with the specified changes. | |||
/// </summary> | |||
/// <remarks> | |||
/// The content of a message can be cleared with String.Empty; if and only if an Embed is present. | |||
/// The content of a message can be cleared with <see cref="string.Empty"/> if and only if an <see cref="Discord.Embed"> is present. | |||
/// </remarks> | |||
/// <example> | |||
/// <code lang="c#"> | |||
/// var message = await ReplyAsync("abc"); | |||
/// await message.ModifyAsync(x => | |||
/// { | |||
/// x.Content = ""; | |||
/// x.Embed = new EmbedBuilder() | |||
/// .WithColor(new Color(40, 40, 120)) | |||
/// .WithAuthor(a => a.Name = "foxbot") | |||
/// .WithTitle("Embed!") | |||
/// .WithDescription("This is an embed."); | |||
/// }); | |||
/// var message = await ReplyAsync("abc"); | |||
/// await message.ModifyAsync(x => | |||
/// { | |||
/// x.Content = ""; | |||
/// x.Embed = new EmbedBuilder() | |||
/// .WithColor(new Color(40, 40, 120)) | |||
/// .WithAuthor(a => a.Name = "foxbot") | |||
/// .WithTitle("Embed!") | |||
/// .WithDescription("This is an embed.") | |||
/// .Build(); | |||
/// }); | |||
/// </code> | |||
/// </example> | |||
public class MessageProperties | |||
@@ -26,7 +27,7 @@ namespace Discord | |||
/// Gets or sets the content of the message. | |||
/// </summary> | |||
/// <remarks> | |||
/// This must be less than 2000 characters. | |||
/// This must be less than the constant defined by <see cref="DiscordConfig.MaxMessageSize"/>. | |||
/// </remarks> | |||
public Optional<string> Content { get; set; } | |||
/// <summary> | |||
@@ -19,7 +19,8 @@ namespace Discord | |||
public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); | |||
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels. </summary> | |||
public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); | |||
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channelType. </summary> | |||
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channel type. </summary> | |||
/// <exception cref="ArgumentException">Unknown channel type.</exception> | |||
public static ChannelPermissions All(IChannel channel) | |||
{ | |||
switch (channel) | |||
@@ -1,7 +1,7 @@ | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Properties that are used to reorder an <see cref="IRole" /> . | |||
/// Properties that are used to reorder an <see cref="IRole" />. | |||
/// </summary> | |||
public class ReorderRoleProperties | |||
{ | |||
@@ -23,7 +23,7 @@ namespace Discord | |||
/// </remarks> | |||
public Optional<string> Name { get; set; } | |||
/// <summary> | |||
/// Gets or sets the role's <see cref="GuildPermission" /> . | |||
/// Gets or sets the role's <see cref="GuildPermission" />. | |||
/// </summary> | |||
public Optional<GuildPermissions> Permissions { get; set; } | |||
/// <summary> | |||
@@ -7,10 +7,10 @@ namespace Discord | |||
/// </summary> | |||
/// <example> | |||
/// <code lang="c#"> | |||
/// await (Context.User as IGuildUser)?.ModifyAsync(x => | |||
/// { | |||
/// x.Nickname = $"festive {Context.User.Username}"; | |||
/// }); | |||
/// await guildUser.ModifyAsync(x => | |||
/// { | |||
/// x.Nickname = $"festive {guildUser.Username}"; | |||
/// }); | |||
/// </code> | |||
/// </example> | |||
/// <seealso cref="T:Discord.IGuildUser" /> | |||
@@ -35,7 +35,7 @@ namespace Discord | |||
/// </summary> | |||
/// <remarks> | |||
/// To clear the user's nickname, this value can be set to <see langword="null" /> or | |||
/// <see cref="string.Empty" /> . | |||
/// <see cref="string.Empty" />. | |||
/// </remarks> | |||
public Optional<string> Nickname { get; set; } | |||
/// <summary> | |||
@@ -27,14 +27,14 @@ namespace Discord | |||
/// Gets or sets the channel for this webhook. | |||
/// </summary> | |||
/// <remarks> | |||
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" /> . | |||
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" />. | |||
/// </remarks> | |||
public Optional<ITextChannel> Channel { get; set; } | |||
/// <summary> | |||
/// Gets or sets the channel ID for this webhook. | |||
/// </summary> | |||
/// <remarks> | |||
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" /> . | |||
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" />. | |||
/// </remarks> | |||
public Optional<ulong> ChannelId { get; set; } | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.Collections.ObjectModel; | |||
@@ -157,12 +157,16 @@ namespace Discord | |||
: this(collection, EqualityComparer<T>.Default) { } | |||
public ConcurrentHashSet(IEqualityComparer<T> comparer) | |||
: this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } | |||
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <see langword="null"/></exception> | |||
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) | |||
: this(comparer) | |||
{ | |||
if (collection == null) throw new ArgumentNullException(nameof(collection)); | |||
InitializeFromCollection(collection); | |||
} | |||
/// <exception cref="ArgumentNullException"> | |||
/// <paramref name="collection" /> or <paramref name="comparer" /> is <see langword="null" /> | |||
/// </exception> | |||
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer) | |||
: this(concurrencyLevel, DefaultCapacity, false, comparer) | |||
{ | |||
@@ -197,7 +201,7 @@ namespace Discord | |||
{ | |||
foreach (var value in collection) | |||
{ | |||
if (value == null) throw new ArgumentNullException("key"); | |||
if (value == null) throw new ArgumentNullException(nameof(value)); | |||
if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) | |||
throw new ArgumentException(); | |||
@@ -206,10 +210,10 @@ namespace Discord | |||
if (_budget == 0) | |||
_budget = _tables._buckets.Length / _tables._locks.Length; | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception> | |||
public bool ContainsKey(T value) | |||
{ | |||
if (value == null) throw new ArgumentNullException("key"); | |||
if (value == null) throw new ArgumentNullException(nameof(value)); | |||
return ContainsKeyInternal(value, _comparer.GetHashCode(value)); | |||
} | |||
private bool ContainsKeyInternal(T value, int hashcode) | |||
@@ -230,9 +234,10 @@ namespace Discord | |||
return false; | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception> | |||
public bool TryAdd(T value) | |||
{ | |||
if (value == null) throw new ArgumentNullException("key"); | |||
if (value == null) throw new ArgumentNullException(nameof(value)); | |||
return TryAddInternal(value, _comparer.GetHashCode(value), true); | |||
} | |||
private bool TryAddInternal(T value, int hashcode, bool acquireLock) | |||
@@ -279,9 +284,10 @@ namespace Discord | |||
} | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception> | |||
public bool TryRemove(T value) | |||
{ | |||
if (value == null) throw new ArgumentNullException("key"); | |||
if (value == null) throw new ArgumentNullException(nameof(value)); | |||
return TryRemoveInternal(value); | |||
} | |||
private bool TryRemoveInternal(T value) | |||
@@ -467,4 +473,4 @@ namespace Discord | |||
Monitor.Exit(_tables._locks[i]); | |||
} | |||
} | |||
} | |||
} |
@@ -11,6 +11,7 @@ namespace Discord | |||
private readonly T _value; | |||
/// <summary> Gets the value for this parameter. </summary> | |||
/// <exception cref="InvalidOperationException" accessor="get">This property has no value set.</exception> | |||
public T Value | |||
{ | |||
get | |||
@@ -183,7 +183,7 @@ namespace Discord | |||
} | |||
// Bulk Delete | |||
/// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks..</exception> | |||
/// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks.</exception> | |||
public static void YoungerThanTwoWeeks(ulong[] collection, string name) | |||
{ | |||
var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); | |||
@@ -194,7 +194,7 @@ namespace Discord | |||
throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | |||
} | |||
} | |||
/// <exception cref="ArgumentException">The everyone role cannot be assigned to a user</exception> | |||
/// <exception cref="ArgumentException">The everyone role cannot be assigned to a user.</exception> | |||
public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) | |||
{ | |||
for (var i = 0; i < roles.Length; i++) | |||
@@ -1,3 +1,4 @@ | |||
using System; | |||
using Discord.API.Rest; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -24,6 +25,7 @@ namespace Discord.Rest | |||
return RestChannel.Create(client, model); | |||
return null; | |||
} | |||
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception> | |||
public static async Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(BaseDiscordClient client, RequestOptions options) | |||
{ | |||
var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false); | |||
@@ -58,6 +58,9 @@ namespace Discord.API | |||
SetBaseUrl(DiscordConfig.APIUrl); | |||
} | |||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
/// <exception cref="Exception">A delegate callback throws an exception.</exception> | |||
internal void SetBaseUrl(string baseUrl) | |||
{ | |||
RestClient = _restClientProvider(baseUrl); | |||
@@ -65,6 +68,7 @@ namespace Discord.API | |||
RestClient.SetHeader("user-agent", UserAgent); | |||
RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); | |||
} | |||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
internal static string GetPrefixedToken(TokenType tokenType, string token) | |||
{ | |||
switch (tokenType) | |||
@@ -76,7 +80,7 @@ namespace Discord.API | |||
case TokenType.Bearer: | |||
return $"Bearer {token}"; | |||
default: | |||
throw new ArgumentException("Unknown OAuth token type", nameof(tokenType)); | |||
throw new ArgumentException("Unknown OAuth token type.", nameof(tokenType)); | |||
} | |||
} | |||
internal virtual void Dispose(bool disposing) | |||
@@ -463,6 +467,7 @@ namespace Discord.API | |||
endpoint = () => $"channels/{channelId}/messages?limit={limit}"; | |||
return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, 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> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotNull(args, nameof(args)); | |||
@@ -471,12 +476,14 @@ namespace Discord.API | |||
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(channelId: channelId); | |||
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", 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<Message> CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null) | |||
{ | |||
if (AuthTokenType != TokenType.Webhook) | |||
@@ -488,11 +495,12 @@ namespace Discord.API | |||
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendJsonAsync<Message>("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), 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> | |||
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotNull(args, nameof(args)); | |||
@@ -507,6 +515,9 @@ namespace Discord.API | |||
var ids = new BucketIds(channelId: channelId); | |||
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), 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<Message> UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null) | |||
{ | |||
if (AuthTokenType != TokenType.Webhook) | |||
@@ -559,6 +570,7 @@ namespace Discord.API | |||
break; | |||
} | |||
} | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
@@ -1267,6 +1279,7 @@ namespace Discord.API | |||
} | |||
//Helpers | |||
/// <exception cref="InvalidOperationException">Client is not logged in.</exception> | |||
protected void CheckState() | |||
{ | |||
if (LoginState != LoginState.LoggedIn) | |||
@@ -22,12 +22,14 @@ namespace Discord.Rest | |||
ApiClient.Dispose(); | |||
} | |||
/// <inheritdoc /> | |||
internal override async Task OnLoginAsync(TokenType tokenType, string token) | |||
{ | |||
var user = await ApiClient.GetMyUserAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); | |||
ApiClient.CurrentUserId = user.Id; | |||
base.CurrentUser = RestSelfUser.Create(this, user); | |||
} | |||
/// <inheritdoc /> | |||
internal override Task OnLogoutAsync() | |||
{ | |||
_applicationInfo = null; | |||
@@ -80,9 +82,11 @@ namespace Discord.Rest | |||
=> ClientHelper.GetWebhookAsync(this, id, options); | |||
//IDiscordClient | |||
/// <inheritdoc /> | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
=> await GetApplicationInfoAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -90,6 +94,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -97,6 +102,7 @@ namespace Discord.Rest | |||
else | |||
return ImmutableArray.Create<IPrivateChannel>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -104,6 +110,7 @@ namespace Discord.Rest | |||
else | |||
return ImmutableArray.Create<IDMChannel>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -112,12 +119,15 @@ namespace Discord.Rest | |||
return ImmutableArray.Create<IGroupChannel>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> await GetConnectionsAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -125,6 +135,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -132,9 +143,11 @@ namespace Discord.Rest | |||
else | |||
return ImmutableArray.Create<IGuild>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
=> await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -143,11 +156,14 @@ namespace Discord.Rest | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
=> await GetVoiceRegionsAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
=> await GetVoiceRegionAsync(id, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | |||
=> await GetWebhookAsync(id, options).ConfigureAwait(false); | |||
} | |||
@@ -7,7 +7,6 @@ using System.Linq; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Channel; | |||
using UserModel = Discord.API.User; | |||
using WebhookModel = Discord.API.Webhook; | |||
namespace Discord.Rest | |||
{ | |||
@@ -77,14 +76,8 @@ namespace Discord.Rest | |||
int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) | |||
{ | |||
var args = new CreateChannelInviteParams { IsTemporary = isTemporary, IsUnique = isUnique }; | |||
if (maxAge.HasValue) | |||
args.MaxAge = maxAge.Value; | |||
else | |||
args.MaxAge = 0; | |||
if (maxUses.HasValue) | |||
args.MaxUses = maxUses.Value; | |||
else | |||
args.MaxUses = 0; | |||
args.MaxAge = maxAge.GetValueOrDefault(0); | |||
args.MaxUses = maxUses.GetValueOrDefault(0); | |||
var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); | |||
return RestInviteMetadata.Create(client, null, channel, model); | |||
} | |||
@@ -160,6 +153,7 @@ namespace Discord.Rest | |||
return builder.ToImmutable(); | |||
} | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | |||
string text, bool isTTS, Embed embed, RequestOptions options) | |||
{ | |||
@@ -169,6 +163,30 @@ namespace Discord.Rest | |||
} | |||
#if FILESYSTEM | |||
/// <exception cref="ArgumentException"> | |||
/// <paramref name="filePath" /> is a zero-length string, contains only white space, or contains one or more | |||
/// invalid characters as defined by <see cref="System.IO.Path.InvalidPathChars" />. | |||
/// </exception> | |||
/// <exception cref="ArgumentNullException"> | |||
/// <paramref name="filePath" /> is <see langword="null" />. | |||
/// </exception> | |||
/// <exception cref="PathTooLongException"> | |||
/// The specified path, file name, or both exceed the system-defined maximum length. For example, on | |||
/// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 | |||
/// characters. | |||
/// </exception> | |||
/// <exception cref="DirectoryNotFoundException"> | |||
/// The specified path is invalid, (for example, it is on an unmapped drive). | |||
/// </exception> | |||
/// <exception cref="UnauthorizedAccessException"> | |||
/// <paramref name="filePath" /> specified a directory.-or- The caller does not have the required permission. | |||
/// </exception> | |||
/// <exception cref="FileNotFoundException"> | |||
/// The file specified in <paramref name="filePath" /> was not found. | |||
/// </exception> | |||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | |||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | |||
string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||
{ | |||
@@ -177,6 +195,7 @@ namespace Discord.Rest | |||
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
} | |||
#endif | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | |||
Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||
{ | |||
@@ -249,6 +268,7 @@ namespace Discord.Rest | |||
return user; | |||
} | |||
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception> | |||
public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, | |||
ulong? fromUserId, int? limit, RequestOptions options) | |||
{ | |||
@@ -5,7 +5,7 @@ using System.Threading.Tasks; | |||
namespace Discord.Rest | |||
{ | |||
/// <summary> | |||
/// Represents a REST channel that can send and receive messages. | |||
/// Represents a REST-based channel that can send and receive messages. | |||
/// </summary> | |||
public interface IRestMessageChannel : IMessageChannel | |||
{ | |||
@@ -3,7 +3,7 @@ using System.Collections.Generic; | |||
namespace Discord.Rest | |||
{ | |||
/// <summary> | |||
/// Represents a REST channel that is private to select recipients. | |||
/// Represents a REST-based channel that is private to select recipients. | |||
/// </summary> | |||
public interface IRestPrivateChannel : IPrivateChannel | |||
{ | |||
@@ -7,7 +7,7 @@ using Model = Discord.API.Channel; | |||
namespace Discord.Rest | |||
{ | |||
/// <summary> | |||
/// Represents a REST category channel. | |||
/// Represents a REST-based category channel. | |||
/// </summary> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestCategoryChannel : RestGuildChannel, ICategoryChannel | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
@@ -8,12 +8,14 @@ namespace Discord.Rest | |||
{ | |||
public class RestChannel : RestEntity<ulong>, IChannel, IUpdateable | |||
{ | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
internal RestChannel(BaseDiscordClient discord, ulong id) | |||
: base(discord, id) | |||
{ | |||
} | |||
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception> | |||
internal static RestChannel Create(BaseDiscordClient discord, Model model) | |||
{ | |||
switch (model.Type) | |||
@@ -28,6 +30,7 @@ namespace Discord.Rest | |||
return new RestChannel(discord, model.Id); | |||
} | |||
} | |||
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception> | |||
internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) | |||
{ | |||
switch (model.Type) | |||
@@ -42,13 +45,17 @@ namespace Discord.Rest | |||
} | |||
internal virtual void Update(Model model) { } | |||
/// <inheritdoc /> | |||
public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); | |||
//IChannel | |||
/// <inheritdoc /> | |||
string IChannel.Name => null; | |||
/// <inheritdoc /> | |||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IUser>(null); //Overridden | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden | |||
} | |||
@@ -10,7 +10,7 @@ using Model = Discord.API.Channel; | |||
namespace Discord.Rest | |||
{ | |||
/// <summary> | |||
/// Represents a REST DM channel. | |||
/// Represents a REST-based DM channel. | |||
/// </summary> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel | |||
@@ -37,6 +37,7 @@ namespace Discord.Rest | |||
Recipient.Update(model.Recipients.Value[0]); | |||
} | |||
/// <inheritdoc /> | |||
public override async Task UpdateAsync(RequestOptions options = null) | |||
{ | |||
var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); | |||
@@ -93,16 +94,20 @@ namespace Discord.Rest | |||
public override string ToString() => $"@{Recipient}"; | |||
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | |||
//IDMChannel | |||
//IDMChannel | |||
/// <inheritdoc /> | |||
IUser IDMChannel.Recipient => Recipient; | |||
//IRestPrivateChannel | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||
//IPrivateChannel | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | |||
//IMessageChannel | |||
/// <inheritdoc /> | |||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -110,6 +115,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -117,6 +123,7 @@ namespace Discord.Rest | |||
else | |||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>(); | |||
} | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -124,6 +131,7 @@ namespace Discord.Rest | |||
else | |||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>(); | |||
} | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -131,25 +139,33 @@ namespace Discord.Rest | |||
else | |||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | |||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | |||
#if FILESYSTEM | |||
/// <inheritdoc /> | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
#endif | |||
/// <inheritdoc /> | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
//IChannel | |||
/// <inheritdoc /> | |||
string IChannel.Name => $"@{Recipient}"; | |||
/// <inheritdoc /> | |||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IUser>(GetUser(id)); | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||
} | |||
@@ -146,6 +146,7 @@ namespace Discord.Rest | |||
public override string ToString() => Name; | |||
//IGuildChannel | |||
/// <inheritdoc /> | |||
IGuild IGuildChannel.Guild | |||
{ | |||
get | |||
@@ -156,32 +157,44 @@ namespace Discord.Rest | |||
} | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options) | |||
=> await GetInvitesAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) | |||
=> await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) | |||
=> GetPermissionOverwrite(role); | |||
/// <inheritdoc /> | |||
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) | |||
=> GetPermissionOverwrite(user); | |||
/// <inheritdoc /> | |||
async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) | |||
=> await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) | |||
=> await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) | |||
=> await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) | |||
=> await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice | |||
/// <inheritdoc /> | |||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice | |||
//IChannel | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden in Text/Voice | |||
/// <inheritdoc /> | |||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IUser>(null); //Overridden in Text/Voice | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using Discord.API.Rest; | |||
using Discord.API.Rest; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -14,10 +14,11 @@ namespace Discord.Rest | |||
internal static class GuildHelper | |||
{ | |||
//General | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public static async Task<Model> ModifyAsync(IGuild guild, BaseDiscordClient client, | |||
Action<GuildProperties> func, RequestOptions options) | |||
{ | |||
if (func == null) throw new NullReferenceException(nameof(func)); | |||
if (func == null) throw new ArgumentNullException(nameof(func)); | |||
var args = new GuildProperties(); | |||
func(args); | |||
@@ -62,10 +63,11 @@ namespace Discord.Rest | |||
return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, | |||
Action<GuildEmbedProperties> func, RequestOptions options) | |||
{ | |||
if (func == null) throw new NullReferenceException(nameof(func)); | |||
if (func == null) throw new ArgumentNullException(nameof(func)); | |||
var args = new GuildEmbedProperties(); | |||
func(args); | |||
@@ -139,6 +141,7 @@ namespace Discord.Rest | |||
var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id, options).ConfigureAwait(false); | |||
return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> | |||
public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, | |||
string name, RequestOptions options) | |||
{ | |||
@@ -148,6 +151,7 @@ namespace Discord.Rest | |||
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | |||
return RestTextChannel.Create(client, guild, model); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> | |||
public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, | |||
string name, RequestOptions options) | |||
{ | |||
@@ -157,6 +161,7 @@ namespace Discord.Rest | |||
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | |||
return RestVoiceChannel.Create(client, guild, model); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> | |||
public static async Task<RestCategoryChannel> CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, | |||
string name, RequestOptions options) | |||
{ | |||
@@ -191,6 +196,7 @@ namespace Discord.Rest | |||
} | |||
//Roles | |||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> | |||
public static async Task<RestRole> CreateRoleAsync(IGuild guild, BaseDiscordClient client, | |||
string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | |||
{ | |||
@@ -292,11 +298,12 @@ namespace Discord.Rest | |||
Image = image.ToModel() | |||
}; | |||
if (roles.IsSpecified) | |||
apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray(); | |||
apiargs.RoleIds = roles.Value?.Select(xr => xr.Id).ToArray(); | |||
var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options).ConfigureAwait(false); | |||
return emote.ToEntity(); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public static async Task<GuildEmote> ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action<EmoteProperties> func, | |||
RequestOptions options) | |||
{ | |||
@@ -310,7 +317,7 @@ namespace Discord.Rest | |||
Name = props.Name | |||
}; | |||
if (props.Roles.IsSpecified) | |||
apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray(); | |||
apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id).ToArray(); | |||
var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options).ConfigureAwait(false); | |||
return emote.ToEntity(); | |||
@@ -1,4 +1,4 @@ | |||
using System.Diagnostics; | |||
using System.Diagnostics; | |||
using Model = Discord.API.Ban; | |||
namespace Discord.Rest | |||
@@ -7,6 +7,7 @@ namespace Discord.Rest | |||
public class RestBan : IBan | |||
{ | |||
public RestUser User { get; } | |||
/// <inheritdoc /> | |||
public string Reason { get; } | |||
internal RestBan(RestUser user, string reason) | |||
@@ -23,6 +24,7 @@ namespace Discord.Rest | |||
private string DebuggerDisplay => $"{User}: {Reason}"; | |||
//IBan | |||
/// <inheritdoc /> | |||
IUser IBan.User => User; | |||
} | |||
} |
@@ -10,6 +10,9 @@ using Model = Discord.API.Guild; | |||
namespace Discord.Rest | |||
{ | |||
/// <summary> | |||
/// Represents a REST-based guild/server. | |||
/// </summary> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestGuild : RestEntity<ulong>, IGuild, IUpdateable | |||
{ | |||
@@ -17,32 +20,50 @@ namespace Discord.Rest | |||
private ImmutableArray<GuildEmote> _emotes; | |||
private ImmutableArray<string> _features; | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public int AFKTimeout { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsEmbeddable { get; private set; } | |||
/// <inheritdoc /> | |||
public VerificationLevel VerificationLevel { get; private set; } | |||
/// <inheritdoc /> | |||
public MfaLevel MfaLevel { get; private set; } | |||
/// <inheritdoc /> | |||
public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? AFKChannelId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? EmbedChannelId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? SystemChannelId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong OwnerId { get; private set; } | |||
/// <inheritdoc /> | |||
public string VoiceRegionId { get; private set; } | |||
/// <inheritdoc /> | |||
public string IconId { get; private set; } | |||
/// <inheritdoc /> | |||
public string SplashId { get; private set; } | |||
internal bool Available { get; private set; } | |||
/// <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 /> | |||
public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); | |||
public RestRole EveryoneRole => GetRole(Id); | |||
public IReadOnlyCollection<RestRole> Roles => _roles.ToReadOnlyCollection(); | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<GuildEmote> Emotes => _emotes; | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<string> Features => _features; | |||
internal RestGuild(BaseDiscordClient client, ulong id) | |||
@@ -103,37 +124,48 @@ namespace Discord.Rest | |||
} | |||
//General | |||
/// <inheritdoc /> | |||
public async Task UpdateAsync(RequestOptions options = null) | |||
=> Update(await Discord.ApiClient.GetGuildAsync(Id, options).ConfigureAwait(false)); | |||
/// <inheritdoc /> | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> GuildHelper.DeleteAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public async Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
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="args" /> is <see langword="null" />.</exception> | |||
public async Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null) | |||
{ | |||
var arr = args.ToArray(); | |||
await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options).ConfigureAwait(false); | |||
} | |||
/// <inheritdoc /> | |||
public async Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null) | |||
{ | |||
var models = await GuildHelper.ReorderRolesAsync(this, Discord, args, options).ConfigureAwait(false); | |||
foreach (var model in models) | |||
{ | |||
var role = GetRole(model.Id); | |||
if (role != null) | |||
role.Update(model); | |||
role?.Update(model); | |||
} | |||
} | |||
/// <inheritdoc /> | |||
public Task LeaveAsync(RequestOptions options = null) | |||
=> GuildHelper.LeaveAsync(this, Discord, options); | |||
@@ -141,13 +173,17 @@ namespace Discord.Rest | |||
public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null) | |||
=> GuildHelper.GetBansAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) | |||
=> GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); | |||
/// <inheritdoc /> | |||
public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) | |||
=> GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); | |||
/// <inheritdoc /> | |||
public Task RemoveBanAsync(IUser user, RequestOptions options = null) | |||
=> GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); | |||
/// <inheritdoc /> | |||
public Task RemoveBanAsync(ulong userId, RequestOptions options = null) | |||
=> GuildHelper.RemoveBanAsync(this, Discord, userId, options); | |||
@@ -261,6 +297,7 @@ namespace Discord.Rest | |||
public Task<RestGuildUser> GetOwnerAsync(RequestOptions options = null) | |||
=> GuildHelper.GetUserAsync(this, Discord, OwnerId, options); | |||
/// <inheritdoc /> | |||
public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) | |||
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); | |||
@@ -270,28 +307,45 @@ namespace Discord.Rest | |||
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | |||
=> GuildHelper.GetWebhooksAsync(this, Discord, options); | |||
/// <summary> | |||
/// Returns the name of the guild. | |||
/// </summary> | |||
/// <returns> | |||
/// The name of the guild. | |||
/// </returns> | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name} ({Id})"; | |||
//Emotes | |||
/// <inheritdoc /> | |||
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | |||
=> GuildHelper.GetEmoteAsync(this, Discord, id, options); | |||
/// <inheritdoc /> | |||
public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null) | |||
=> GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null" />.</exception> | |||
public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); | |||
/// <inheritdoc /> | |||
public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) | |||
=> GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); | |||
//IGuild | |||
/// <inheritdoc /> | |||
bool IGuild.Available => Available; | |||
/// <inheritdoc /> | |||
IAudioClient IGuild.AudioClient => null; | |||
/// <inheritdoc /> | |||
IRole IGuild.EveryoneRole => EveryoneRole; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IRole> IGuild.Roles => Roles; | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | |||
=> await GetBansAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -299,6 +353,7 @@ namespace Discord.Rest | |||
else | |||
return ImmutableArray.Create<IGuildChannel>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -306,6 +361,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<ITextChannel>> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -313,6 +369,7 @@ namespace Discord.Rest | |||
else | |||
return ImmutableArray.Create<ITextChannel>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -320,6 +377,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -327,6 +385,7 @@ namespace Discord.Rest | |||
else | |||
return ImmutableArray.Create<IVoiceChannel>(); | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<ICategoryChannel>> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -334,6 +393,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -341,6 +401,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -348,6 +409,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -355,6 +417,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -362,6 +425,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<ITextChannel> IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -369,26 +433,35 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name, RequestOptions options) | |||
=> await CreateTextChannelAsync(name, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) | |||
=> await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, RequestOptions options) | |||
=> await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options) | |||
=> await GetIntegrationsAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) | |||
=> await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options) | |||
=> await GetInvitesAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
IRole IGuild.GetRole(ulong id) | |||
=> GetRole(id); | |||
/// <inheritdoc /> | |||
async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | |||
=> await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -396,6 +469,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IGuildUser> IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -403,6 +477,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IGuildUser> IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -410,6 +485,7 @@ namespace Discord.Rest | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -418,12 +494,14 @@ namespace Discord.Rest | |||
return ImmutableArray.Create<IGuildUser>(); | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Downloading users is not supported with a REST-based guild.</exception> | |||
/// <exception cref="NotSupportedException">Downloading users is not supported for a REST-based guild.</exception> | |||
Task IGuild.DownloadUsersAsync() => | |||
throw new NotSupportedException(); | |||
/// <inheritdoc /> | |||
async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options) | |||
=> await GetWebhookAsync(id, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) | |||
=> await GetWebhooksAsync(options).ConfigureAwait(false); | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using System.Diagnostics; | |||
using System.Diagnostics; | |||
using Model = Discord.API.GuildEmbed; | |||
namespace Discord | |||
@@ -19,7 +19,7 @@ namespace Discord | |||
return new RestGuildEmbed(model.Enabled, model.ChannelId); | |||
} | |||
public override string ToString() => ChannelId?.ToString(); | |||
public override string ToString() => ChannelId?.ToString() ?? "Unknown"; | |||
private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})"; | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Integration; | |||
@@ -10,18 +10,28 @@ namespace Discord.Rest | |||
{ | |||
private long _syncedAtTicks; | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public string Type { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsEnabled { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsSyncing { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong ExpireBehavior { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong ExpireGracePeriod { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong GuildId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong RoleId { get; private set; } | |||
public RestUser User { get; private set; } | |||
/// <inheritdoc /> | |||
public IntegrationAccount Account { get; private set; } | |||
internal IGuild Guild { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); | |||
internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id) | |||
@@ -78,6 +88,7 @@ namespace Discord.Rest | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; | |||
/// <inheritdoc /> | |||
IGuild IGuildIntegration.Guild | |||
{ | |||
get | |||
@@ -87,6 +98,7 @@ namespace Discord.Rest | |||
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
} | |||
} | |||
/// <inheritdoc /> | |||
IUser IGuildIntegration.User => User; | |||
} | |||
} |
@@ -9,12 +9,17 @@ namespace Discord.Rest | |||
public class RestUserGuild : RestEntity<ulong>, IUserGuild | |||
{ | |||
private string _iconId; | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsOwner { get; private set; } | |||
/// <inheritdoc /> | |||
public GuildPermissions Permissions { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
/// <inheritdoc /> | |||
public string IconUrl => CDN.GetGuildIconUrl(Id, _iconId); | |||
internal RestUserGuild(BaseDiscordClient discord, ulong id) | |||
@@ -40,6 +45,7 @@ namespace Discord.Rest | |||
{ | |||
await Discord.ApiClient.LeaveGuildAsync(Id, options).ConfigureAwait(false); | |||
} | |||
/// <inheritdoc /> | |||
public async Task DeleteAsync(RequestOptions options = null) | |||
{ | |||
await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false); | |||
@@ -59,7 +59,8 @@ namespace Discord.Rest | |||
public override string ToString() => Url; | |||
private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; | |||
/// <inheritdoc /> | |||
IGuild IInvite.Guild | |||
{ | |||
get | |||
@@ -71,6 +72,7 @@ namespace Discord.Rest | |||
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
} | |||
} | |||
/// <inheritdoc /> | |||
IChannel IInvite.Channel | |||
{ | |||
get | |||
@@ -10,11 +10,13 @@ namespace Discord.Rest | |||
{ | |||
internal static class MessageHelper | |||
{ | |||
/// <exception cref="InvalidOperationException">Only the author of a message may modify the message.</exception> | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public static async Task<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func, | |||
RequestOptions options) | |||
{ | |||
if (msg.Author.Id != client.CurrentUser.Id) | |||
throw new InvalidOperationException("Only the author of a message may change it."); | |||
throw new InvalidOperationException("Only the author of a message may modify the message."); | |||
var args = new MessageProperties(); | |||
func(args); | |||
@@ -35,6 +35,7 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | |||
public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | |||
/// <inheritdoc /> | |||
@@ -75,10 +76,14 @@ namespace Discord.Rest | |||
public override string ToString() => Content; | |||
/// <inheritdoc /> | |||
MessageType IMessage.Type => MessageType.Default; | |||
IUser IMessage.Author => Author; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | |||
} | |||
} |
@@ -17,23 +17,33 @@ namespace Discord.Rest | |||
private ImmutableArray<Embed> _embeds; | |||
private ImmutableArray<ITag> _tags; | |||
private ImmutableArray<RestReaction> _reactions; | |||
/// <inheritdoc /> | |||
public override bool IsTTS => _isTTS; | |||
/// <inheritdoc /> | |||
public override bool IsPinned => _isPinned; | |||
/// <inheritdoc /> | |||
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<Attachment> Attachments => _attachments; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<Embed> Embeds => _embeds; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ulong> MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ITag> Tags => _tags; | |||
/// <inheritdoc /> | |||
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); | |||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||
: base(discord, id, channel, author, source) | |||
{ | |||
} | |||
internal static new RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
{ | |||
var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | |||
entity.Update(model); | |||
@@ -124,30 +134,37 @@ namespace Discord.Rest | |||
} | |||
} | |||
/// <inheritdoc /> | |||
public async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
public Task AddReactionAsync(IEmote emote, RequestOptions options = null) | |||
=> MessageHelper.AddReactionAsync(this, emote, Discord, options); | |||
/// <inheritdoc /> | |||
public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) | |||
=> MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); | |||
/// <inheritdoc /> | |||
public Task RemoveAllReactionsAsync(RequestOptions options = null) | |||
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) | |||
=> MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options); | |||
/// <inheritdoc /> | |||
public Task PinAsync(RequestOptions options = null) | |||
=> MessageHelper.PinAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task UnpinAsync(RequestOptions options = null) | |||
=> MessageHelper.UnpinAsync(this, Discord, 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) | |||
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
/// <inheritdoc /> | |||
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
@@ -19,6 +19,7 @@ namespace Discord.Rest | |||
public string Description { get; private set; } | |||
/// <inheritdoc /> | |||
public string[] RPCOrigins { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong Flags { get; private set; } | |||
/// <inheritdoc /> | |||
@@ -52,6 +53,7 @@ namespace Discord.Rest | |||
Owner = RestUser.Create(Discord, model.Owner.Value); | |||
} | |||
/// <exception cref="InvalidOperationException">Unable to update this object from a different application token.</exception> | |||
public async Task UpdateAsync() | |||
{ | |||
var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); | |||
@@ -60,6 +62,12 @@ namespace Discord.Rest | |||
Update(response); | |||
} | |||
/// <summary> | |||
/// Gets the name of the application. | |||
/// </summary> | |||
/// <returns> | |||
/// Name of the application. | |||
/// </returns> | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name} ({Id})"; | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Role; | |||
@@ -9,16 +9,25 @@ namespace Discord.Rest | |||
public class RestRole : RestEntity<ulong>, IRole | |||
{ | |||
internal IGuild Guild { get; } | |||
/// <inheritdoc /> | |||
public Color Color { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsHoisted { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsManaged { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsMentionable { get; private set; } | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public GuildPermissions Permissions { get; private set; } | |||
/// <inheritdoc /> | |||
public int Position { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
public bool IsEveryone => Id == Guild.Id; | |||
/// <inheritdoc /> | |||
public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); | |||
internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) | |||
@@ -43,20 +52,24 @@ namespace Discord.Rest | |||
Permissions = new GuildPermissions(model.Permissions); | |||
} | |||
/// <inheritdoc /> | |||
public async Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> RoleHelper.DeleteAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public int CompareTo(IRole role) => RoleUtils.Compare(this, role); | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name} ({Id})"; | |||
//IRole | |||
/// <inheritdoc /> | |||
IGuild IRole.Guild | |||
{ | |||
get | |||
@@ -1,4 +1,4 @@ | |||
using System.Collections.Generic; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using Model = Discord.API.Connection; | |||
@@ -8,10 +8,15 @@ namespace Discord | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestConnection : IConnection | |||
{ | |||
/// <inheritdoc /> | |||
public string Id { get; } | |||
/// <inheritdoc /> | |||
public string Type { get; } | |||
/// <inheritdoc /> | |||
public string Name { get; } | |||
/// <inheritdoc /> | |||
public bool IsRevoked { get; } | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<ulong> IntegrationIds { get; } | |||
internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection<ulong> integrationIds) | |||
@@ -28,6 +33,12 @@ namespace Discord | |||
return new RestConnection(model.Id, model.Type, model.Name, model.Revoked, model.Integrations.ToImmutableArray()); | |||
} | |||
/// <summary> | |||
/// Gets the name of the connection. | |||
/// </summary> | |||
/// <returns> | |||
/// Name of the connection. | |||
/// </returns> | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; | |||
} | |||
@@ -24,7 +24,9 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public ulong GuildId => Guild.Id; | |||
/// <inheritdoc /> | |||
/// <exception cref="InvalidOperationException" accessor="get">Resolving permissions requires the parent guild to be downloaded.</exception> | |||
public GuildPermissions GuildPermissions | |||
{ | |||
get | |||
@@ -112,6 +114,7 @@ namespace Discord.Rest | |||
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception> | |||
public ChannelPermissions GetPermissions(IGuildChannel channel) | |||
{ | |||
var guildPerms = GuildPermissions; | |||
@@ -119,6 +122,7 @@ namespace Discord.Rest | |||
} | |||
//IGuildUser | |||
/// <inheritdoc /> | |||
IGuild IGuildUser.Guild | |||
{ | |||
get | |||
@@ -130,10 +134,15 @@ namespace Discord.Rest | |||
} | |||
//IVoiceState | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsSelfDeafened => false; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsSelfMuted => false; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsSuppressed => false; | |||
/// <inheritdoc /> | |||
IVoiceChannel IVoiceState.VoiceChannel => null; | |||
/// <inheritdoc /> | |||
string IVoiceState.VoiceSessionId => null; | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.User; | |||
@@ -8,8 +8,11 @@ namespace Discord.Rest | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestSelfUser : RestUser, ISelfUser | |||
{ | |||
/// <inheritdoc /> | |||
public string Email { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsVerified { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsMfaEnabled { get; private set; } | |||
internal RestSelfUser(BaseDiscordClient discord, ulong id) | |||
@@ -22,6 +25,7 @@ namespace Discord.Rest | |||
entity.Update(model); | |||
return entity; | |||
} | |||
/// <inheritdoc /> | |||
internal override void Update(Model model) | |||
{ | |||
base.Update(model); | |||
@@ -34,6 +38,8 @@ namespace Discord.Rest | |||
IsMfaEnabled = model.MfaEnabled.Value; | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="InvalidOperationException">Unable to update this object using a different token.</exception> | |||
public override async Task UpdateAsync(RequestOptions options = null) | |||
{ | |||
var model = await Discord.ApiClient.GetMyUserAsync(options).ConfigureAwait(false); | |||
@@ -42,6 +48,8 @@ namespace Discord.Rest | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="InvalidOperationException">Unable to modify this object using a different token.</exception> | |||
public async Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) | |||
{ | |||
if (Id != Discord.CurrentUser.Id) | |||
@@ -8,16 +8,26 @@ namespace Discord.Rest | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestUser : RestEntity<ulong>, IUser, IUpdateable | |||
{ | |||
/// <inheritdoc /> | |||
public bool IsBot { get; private set; } | |||
/// <inheritdoc /> | |||
public string Username { get; private set; } | |||
/// <inheritdoc /> | |||
public ushort DiscriminatorValue { get; private set; } | |||
/// <inheritdoc /> | |||
public string AvatarId { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
/// <inheritdoc /> | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
/// <inheritdoc /> | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
/// <inheritdoc /> | |||
public virtual IActivity Activity => null; | |||
/// <inheritdoc /> | |||
public virtual UserStatus Status => UserStatus.Offline; | |||
/// <inheritdoc /> | |||
public virtual bool IsWebhook => false; | |||
internal RestUser(BaseDiscordClient discord, ulong id) | |||
@@ -48,6 +58,7 @@ namespace Discord.Rest | |||
Username = model.Username.Value; | |||
} | |||
/// <inheritdoc /> | |||
public virtual async Task UpdateAsync(RequestOptions options = null) | |||
{ | |||
var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false); | |||
@@ -57,9 +68,11 @@ namespace Discord.Rest | |||
public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) | |||
=> UserHelper.CreateDMChannelAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
/// <inheritdoc /> | |||
public string GetDefaultAvatarUrl() | |||
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); | |||
@@ -67,6 +80,7 @@ namespace Discord.Rest | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
//IUser | |||
/// <inheritdoc /> | |||
async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | |||
=> await GetOrCreateDMChannelAsync(options).ConfigureAwait(false); | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Webhook; | |||
@@ -11,14 +11,21 @@ namespace Discord.Rest | |||
internal IGuild Guild { get; private set; } | |||
internal ITextChannel Channel { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong ChannelId { get; } | |||
/// <inheritdoc /> | |||
public string Token { get; } | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public string AvatarId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? GuildId { get; private set; } | |||
/// <inheritdoc /> | |||
public IUser Creator { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
internal RestWebhook(BaseDiscordClient discord, IGuild guild, ulong id, string token, ulong channelId) | |||
@@ -59,12 +66,14 @@ namespace Discord.Rest | |||
Name = model.Name.Value; | |||
} | |||
/// <inheritdoc /> | |||
public async Task UpdateAsync(RequestOptions options = null) | |||
{ | |||
var model = await Discord.ApiClient.GetWebhookAsync(Id, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
@@ -74,6 +83,7 @@ namespace Discord.Rest | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> WebhookHelper.DeleteAsync(this, Discord, options); | |||
@@ -81,10 +91,13 @@ namespace Discord.Rest | |||
private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; | |||
//IWebhook | |||
/// <inheritdoc /> | |||
IGuild IWebhook.Guild | |||
=> Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
/// <inheritdoc /> | |||
ITextChannel IWebhook.Channel | |||
=> Channel ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
/// <inheritdoc /> | |||
Task IWebhook.ModifyAsync(Action<WebhookProperties> func, RequestOptions options) | |||
=> ModifyAsync(func, options); | |||
} | |||
@@ -82,6 +82,8 @@ namespace Discord.Net.Rest | |||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); | |||
} | |||
} | |||
/// <exception cref="InvalidOperationException">Unsupported param type.</exception> | |||
public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) | |||
{ | |||
string uri = Path.Combine(_baseUrl, endpoint); | |||
@@ -111,7 +113,7 @@ namespace Discord.Net.Rest | |||
content.Add(new StreamContent(stream), p.Key, fileValue.Filename); | |||
continue; | |||
} | |||
default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); | |||
default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"."); | |||
} | |||
} | |||
} | |||
@@ -6,6 +6,7 @@ namespace Discord.Net.Rest | |||
{ | |||
public static readonly RestClientProvider Instance = Create(); | |||
/// <exception cref="PlatformNotSupportedException">The default RestClientProvider is not supported on this platform.</exception> | |||
public static RestClientProvider Create(bool useProxy = false) | |||
{ | |||
return url => | |||
@@ -126,7 +126,7 @@ namespace Discord.Net.Queue | |||
if ((request.Options.RetryMode & RetryMode.RetryTimeouts) == 0) | |||
throw; | |||
await Task.Delay(500); | |||
await Task.Delay(500).ConfigureAwait(false); | |||
continue; //Retry | |||
} | |||
/*catch (Exception) | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.Net | |||
@@ -15,7 +15,7 @@ namespace Discord.Net | |||
internal RateLimitInfo(Dictionary<string, string> headers) | |||
{ | |||
IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && | |||
bool.TryParse(temp, out var isGlobal) ? isGlobal : false; | |||
bool.TryParse(temp, out var isGlobal) && isGlobal; | |||
Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && | |||
int.TryParse(temp, out var limit) ? limit : (int?)null; | |||
Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
@@ -22,19 +22,22 @@ namespace Discord.Audio.Streams | |||
_decoder = new OpusDecoder(); | |||
} | |||
/// <exception cref="InvalidOperationException">Header received with no payload.</exception> | |||
public override void WriteHeader(ushort seq, uint timestamp, bool missed) | |||
{ | |||
if (_hasHeader) | |||
throw new InvalidOperationException("Header received with no payload"); | |||
throw new InvalidOperationException("Header received with no payload."); | |||
_hasHeader = true; | |||
_nextMissed = missed; | |||
_next.WriteHeader(seq, timestamp, missed); | |||
} | |||
/// <exception cref="InvalidOperationException">Received payload without an RTP header.</exception> | |||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | |||
{ | |||
if (!_hasHeader) | |||
throw new InvalidOperationException("Received payload without an RTP header"); | |||
throw new InvalidOperationException("Received payload without an RTP header."); | |||
_hasHeader = false; | |||
if (!_nextMissed) | |||
@@ -1,4 +1,5 @@ | |||
using System.Threading; | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.Audio.Streams | |||
@@ -20,6 +21,8 @@ namespace Discord.Audio.Streams | |||
_nonce = new byte[24]; | |||
} | |||
/// <exception cref="OperationCanceledException">The token has had cancellation requested.</exception> | |||
/// <exception cref="ObjectDisposedException">The associated <see cref="T:System.Threading.CancellationTokenSource" /> has been disposed.</exception> | |||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | |||
{ | |||
cancelToken.ThrowIfCancellationRequested(); | |||
@@ -20,21 +20,25 @@ namespace Discord.Audio.Streams | |||
_client = (AudioClient)client; | |||
_nonce = new byte[24]; | |||
} | |||
/// <exception cref="InvalidOperationException">Header received with no payload.</exception> | |||
public override void WriteHeader(ushort seq, uint timestamp, bool missed) | |||
{ | |||
if (_hasHeader) | |||
throw new InvalidOperationException("Header received with no payload"); | |||
throw new InvalidOperationException("Header received with no payload."); | |||
_nextSeq = seq; | |||
_nextTimestamp = timestamp; | |||
_hasHeader = true; | |||
} | |||
/// <exception cref="InvalidOperationException">Received payload without an RTP header.</exception> | |||
/// <exception cref="OperationCanceledException">The token has had cancellation requested.</exception> | |||
/// <exception cref="ObjectDisposedException">The associated <see cref="T:System.Threading.CancellationTokenSource" /> has been disposed.</exception> | |||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | |||
{ | |||
cancelToken.ThrowIfCancellationRequested(); | |||
if (!_hasHeader) | |||
throw new InvalidOperationException("Received payload without an RTP header"); | |||
throw new InvalidOperationException("Received payload without an RTP header."); | |||
_hasHeader = false; | |||
if (_client.SecretKey == null) | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord.WebSocket | |||
@@ -10,42 +10,174 @@ namespace Discord.WebSocket | |||
{ | |||
protected readonly DiscordSocketConfig BaseConfig; | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
/// <summary> | |||
/// Gets the estimated round-trip latency, in milliseconds, to the gateway server. | |||
/// </summary> | |||
public abstract int Latency { get; protected set; } | |||
public abstract UserStatus Status { get; protected set; } | |||
/// <summary> | |||
/// Gets the status for the logged-in user. | |||
/// </summary> | |||
public abstract UserStatus Status { get; protected set; } | |||
/// <summary> | |||
/// Gets the activity for the logged-in user. | |||
/// </summary> | |||
public abstract IActivity Activity { get; protected set; } | |||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
/// <summary> | |||
/// Gets the current logged-in user. | |||
/// </summary> | |||
public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } | |||
/// <summary> | |||
/// Gets a collection of guilds that the logged-in user is currently in. | |||
/// </summary> | |||
public abstract IReadOnlyCollection<SocketGuild> Guilds { get; } | |||
/// <summary> | |||
/// Gets a collection of private channels that are currently open for the logged-in user. | |||
/// </summary> | |||
public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; } | |||
/// <summary> | |||
/// Gets a collection of available voice regions for the logged-in user. | |||
/// </summary> | |||
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); | |||
/// <summary> | |||
/// Gets a Discord application information for the logged-in user. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// Application information. This reflects your application information you submitted when creating a | |||
/// Discord application via the Developer Portal. | |||
/// </returns> | |||
public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a user who shares a mutual guild with logged-in user with the provided snowflake ID. | |||
/// </summary> | |||
/// <param name="id">The user snowflake ID.</param> | |||
/// <returns> | |||
/// A user who shares a mutual guild with the logged-in user and who is also present in the WebSocket cache; | |||
/// or <see langword="null"/> when the user cannot be found. | |||
/// </returns> | |||
public abstract SocketUser GetUser(ulong id); | |||
/// <summary> | |||
/// Gets a user who shares a mutual guild with the logged-in user with the provided username and discriminator value combo. | |||
/// </summary> | |||
/// <param name="username">The name of the user.</param> | |||
/// <param name="discriminator">The discriminator value of the user.</param> | |||
/// <returns> | |||
/// A user who shares a mutual guild with the logged-in user and who is also present in the WebSocket cache; | |||
/// or <see langword="null"/> when the user cannot be found. | |||
/// </returns> | |||
public abstract SocketUser GetUser(string username, string discriminator); | |||
/// <summary> | |||
/// Gets a channel that the logged-in user is accessible to with the provided ID. | |||
/// </summary> | |||
/// <param name="id">The channel snowflake ID.</param> | |||
/// <returns> | |||
/// A generic channel object (voice, text, category, etc.); or <see langword="null" /> when the channel | |||
/// cannot be found. | |||
/// </returns> | |||
public abstract SocketChannel GetChannel(ulong id); | |||
/// <summary> | |||
/// Gets a guild that the logged-in user is accessible to with the provided ID. | |||
/// </summary> | |||
/// <param name="id">The guild snowflake ID.</param> | |||
/// <returns> | |||
/// A guild; or <see langword="null"/> when the guild cannot be found. | |||
/// </returns> | |||
public abstract SocketGuild GetGuild(ulong id); | |||
/// <summary> | |||
/// Gets a voice region with the provided ID. | |||
/// </summary> | |||
/// <param name="id">The unique identifier of the voice region.</param> | |||
/// <returns> | |||
/// A voice region; or <see langword="null"/> if none can be found. | |||
/// </returns> | |||
public abstract RestVoiceRegion GetVoiceRegion(string id); | |||
/// <inheritdoc /> | |||
public abstract Task StartAsync(); | |||
/// <inheritdoc /> | |||
public abstract Task StopAsync(); | |||
/// <summary> | |||
/// Sets the current status of the logged-in user (e.g. Online, Do not Disturb). | |||
/// </summary> | |||
/// <param name="status">The new status to be set.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
public abstract Task SetStatusAsync(UserStatus status); | |||
/// <summary> | |||
/// Sets the game of the logged-in user. | |||
/// </summary> | |||
/// <param name="name">The name of the game.</param> | |||
/// <param name="streamUrl">If streaming, the URL of the stream. Must be a valid Twitch URL.</param> | |||
/// <param name="type">The type of the game.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing); | |||
/// <summary> | |||
/// Sets the <paramref name="activity"/> of the logged-in user. | |||
/// </summary> | |||
/// <remarks> | |||
/// This method sets the <paramref name="activity"/> of the user. Please note that Rich Presence cannot be | |||
/// set via this method or client. Rich Presence is strictly limited to RPC clients only. Furthermore, | |||
/// Discord will only accept setting of name and the type of activity. | |||
/// </remarks> | |||
/// <param name="activity">The activty to be set.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
public abstract Task SetActivityAsync(IActivity activity); | |||
public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | |||
/// <summary> | |||
/// Attempts to download users into the user cache for the selected guilds. | |||
/// </summary> | |||
/// <param name="guilds">The guilds to download the members from.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/>. | |||
/// </returns> | |||
public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | |||
/// <summary> | |||
/// Creates a guild for the logged-in user who is in less than 10 active guilds. | |||
/// </summary> | |||
/// <remarks> | |||
/// This method creates a new guild on behalf of the logged-in user. Note that due to Discord's limitation, | |||
/// this method will only work for users that are in less than 10 guilds. | |||
/// </remarks> | |||
/// <param name="name">The name of the new guild.</param> | |||
/// <param name="region">The voice region to create the guild with.</param> | |||
/// <param name="jpegIcon">The icon of the guild.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the newly created guild. | |||
/// </returns> | |||
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | |||
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); | |||
/// <summary> | |||
/// Gets the connections that the logged-in user has set up. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing a collection of connections. | |||
/// </returns> | |||
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | |||
=> ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); | |||
/// <summary> | |||
/// Gets an invite with the provided invite identifier. | |||
/// </summary> | |||
/// <param name="inviteId">The invitation identifier.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the invite information. | |||
/// </returns> | |||
public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -72,9 +72,9 @@ namespace Discord.WebSocket | |||
switch (channel) | |||
{ | |||
case SocketDMChannel dmChannel: | |||
_dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); | |||
_dmChannels.TryRemove(dmChannel.Recipient.Id, out var _); | |||
break; | |||
case SocketGroupChannel groupChannel: | |||
case SocketGroupChannel _: | |||
_groupChannels.TryRemove(id); | |||
break; | |||
} | |||
@@ -2,7 +2,9 @@ using Discord.WebSocket; | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> The WebSocket variant of <see cref="ICommandContext"/>, which may contain the client, user, guild, channel, and message. </summary> | |||
/// <summary> | |||
/// Represents a WebSocket-based context of a command. This may include the client, guild, channel, user, and message. | |||
/// </summary> | |||
public class SocketCommandContext : ICommandContext | |||
{ | |||
/// <summary> | |||
@@ -120,12 +120,14 @@ namespace Discord.API | |||
} | |||
finally { _stateLock.Release(); } | |||
} | |||
/// <exception cref="InvalidOperationException">The client must be logged in before connecting.</exception> | |||
/// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception> | |||
internal override async Task ConnectInternalAsync() | |||
{ | |||
if (LoginState != LoginState.LoggedIn) | |||
throw new InvalidOperationException("You must log in before connecting."); | |||
throw new InvalidOperationException("The client must be logged in before connecting."); | |||
if (WebSocketClient == null) | |||
throw new NotSupportedException("This client is not configured with websocket support."); | |||
throw new NotSupportedException("This client is not configured with WebSocket support."); | |||
//Re-create streams to reset the zlib state | |||
_compressed?.Dispose(); | |||
@@ -176,10 +178,11 @@ namespace Discord.API | |||
} | |||
finally { _stateLock.Release(); } | |||
} | |||
/// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception> | |||
internal override async Task DisconnectInternalAsync() | |||
{ | |||
if (WebSocketClient == null) | |||
throw new NotSupportedException("This client is not configured with websocket support."); | |||
throw new NotSupportedException("This client is not configured with WebSocket support."); | |||
if (ConnectionState == ConnectionState.Disconnected) return; | |||
ConnectionState = ConnectionState.Disconnecting; | |||
@@ -46,7 +46,9 @@ namespace Discord.WebSocket | |||
public ConnectionState ConnectionState => _connection.State; | |||
/// <inheritdoc /> | |||
public override int Latency { get; protected set; } | |||
/// <inheritdoc /> | |||
public override UserStatus Status { get; protected set; } = UserStatus.Online; | |||
/// <inheritdoc /> | |||
public override IActivity Activity { get; protected set; } | |||
//From DiscordSocketConfig | |||
@@ -58,14 +60,17 @@ namespace Discord.WebSocket | |||
internal WebSocketProvider WebSocketProvider { get; private set; } | |||
internal bool AlwaysDownloadUsers { get; private set; } | |||
internal int? HandlerTimeout { get; private set; } | |||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels; | |||
public IReadOnlyCollection<SocketDMChannel> DMChannels | |||
=> State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); | |||
public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
/// <summary> Creates a new REST/WebSocket Discord client. </summary> | |||
@@ -128,6 +133,7 @@ namespace Discord.WebSocket | |||
} | |||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); | |||
/// <inheritdoc /> | |||
internal override void Dispose(bool disposing) | |||
{ | |||
if (disposing) | |||
@@ -136,7 +142,8 @@ namespace Discord.WebSocket | |||
ApiClient.Dispose(); | |||
} | |||
} | |||
/// <inheritdoc /> | |||
internal override async Task OnLoginAsync(TokenType tokenType, string token) | |||
{ | |||
if (_parentClient == null) | |||
@@ -147,6 +154,7 @@ namespace Discord.WebSocket | |||
else | |||
_voiceRegions = _parentClient._voiceRegions; | |||
} | |||
/// <inheritdoc /> | |||
internal override async Task OnLogoutAsync() | |||
{ | |||
await StopAsync().ConfigureAwait(false); | |||
@@ -154,8 +162,10 @@ namespace Discord.WebSocket | |||
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||
} | |||
/// <inheritdoc /> | |||
public override async Task StartAsync() | |||
=> await _connection.StartAsync().ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
public override async Task StopAsync() | |||
=> await _connection.StopAsync().ConfigureAwait(false); | |||
@@ -277,7 +287,7 @@ namespace Discord.WebSocket | |||
return null; | |||
} | |||
/// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | |||
/// <inheritdoc /> | |||
public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | |||
{ | |||
if (ConnectionState == ConnectionState.Connected) | |||
@@ -316,6 +326,7 @@ namespace Discord.WebSocket | |||
} | |||
} | |||
/// <inheritdoc /> | |||
public override async Task SetStatusAsync(UserStatus status) | |||
{ | |||
Status = status; | |||
@@ -325,6 +336,7 @@ namespace Discord.WebSocket | |||
_statusSince = null; | |||
await SendStatusAsync().ConfigureAwait(false); | |||
} | |||
/// <inheritdoc /> | |||
public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) | |||
{ | |||
if (!string.IsNullOrEmpty(streamUrl)) | |||
@@ -335,6 +347,7 @@ namespace Discord.WebSocket | |||
Activity = null; | |||
await SendStatusAsync().ConfigureAwait(false); | |||
} | |||
/// <inheritdoc /> | |||
public override async Task SetActivityAsync(IActivity activity) | |||
{ | |||
Activity = activity; | |||
@@ -351,8 +364,8 @@ namespace Discord.WebSocket | |||
var gameModel = new GameModel(); | |||
// Discord only accepts rich presence over RPC, don't even bother building a payload | |||
if (Activity is RichGame game) | |||
throw new NotSupportedException("Outgoing Rich Presences are not supported"); | |||
if (Activity is RichGame) | |||
throw new NotSupportedException("Outgoing Rich Presences are not supported via WebSocket."); | |||
if (Activity != null) | |||
{ | |||
@@ -479,7 +492,7 @@ namespace Discord.WebSocket | |||
await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); | |||
await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | |||
}); | |||
var _ = _connection.CompleteAsync(); | |||
_ = _connection.CompleteAsync(); | |||
} | |||
break; | |||
case "RESUMED": | |||
@@ -1173,7 +1186,7 @@ namespace Discord.WebSocket | |||
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)); | |||
var cacheable = new Cacheable<IMessage, ulong>(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); | |||
} | |||
@@ -1609,6 +1622,7 @@ namespace Discord.WebSocket | |||
return guild; | |||
} | |||
/// <exception cref="InvalidOperationException">Unexpected channel type is created.</exception> | |||
internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) | |||
{ | |||
var channel = SocketChannel.CreatePrivate(this, state, model); | |||
@@ -1781,43 +1795,59 @@ namespace Discord.WebSocket | |||
internal int GetAudioId() => _nextAudioId++; | |||
//IDiscordClient | |||
/// <inheritdoc /> | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
=> await GetApplicationInfoAsync().ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IChannel>(GetChannel(id)); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IDMChannel>>(DMChannels); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IGroupChannel>>(GroupChannels); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> await GetConnectionsAsync().ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> await GetInviteAsync(inviteId).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuild>(GetGuild(id)); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | |||
/// <inheritdoc /> | |||
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IUser>(GetUser(id)); | |||
/// <inheritdoc /> | |||
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | |||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
/// <inheritdoc /> | |||
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
/// <inheritdoc /> | |||
async Task IDiscordClient.StartAsync() | |||
=> await StartAsync().ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task IDiscordClient.StopAsync() | |||
=> await StopAsync().ConfigureAwait(false); | |||
} | |||
@@ -58,6 +58,7 @@ namespace Discord.WebSocket | |||
internal abstract SocketUser GetUserInternal(ulong id); | |||
internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal(); | |||
private string DebuggerDisplay => $"Unknown ({Id}, Channel)"; | |||
internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; | |||
//IChannel | |||
@@ -1,4 +1,4 @@ | |||
using Discord.Rest; | |||
using Discord.Rest; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -57,7 +57,7 @@ namespace Discord.WebSocket | |||
else | |||
return ImmutableArray.Create<SocketMessage>(); | |||
} | |||
/// <exception cref="NotSupportedException">Unexpected <see cref="ISocketMessageChannel"/> type.</exception> | |||
public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, | |||
SocketMessage msg) | |||
{ | |||
@@ -66,9 +66,10 @@ namespace Discord.WebSocket | |||
case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break; | |||
case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break; | |||
case SocketTextChannel textChannel: textChannel.AddMessage(msg); break; | |||
default: throw new NotSupportedException("Unexpected ISocketMessageChannel type"); | |||
default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."); | |||
} | |||
} | |||
/// <exception cref="NotSupportedException">Unexpected <see cref="ISocketMessageChannel"/> type.</exception> | |||
public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, | |||
ulong id) | |||
{ | |||
@@ -77,7 +78,7 @@ namespace Discord.WebSocket | |||
case SocketDMChannel dmChannel: return dmChannel.RemoveMessage(id); | |||
case SocketGroupChannel groupChannel: return groupChannel.RemoveMessage(id); | |||
case SocketTextChannel textChannel: return textChannel.RemoveMessage(id); | |||
default: throw new NotSupportedException("Unexpected ISocketMessageChannel type"); | |||
default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."); | |||
} | |||
} | |||
} | |||
@@ -111,7 +111,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public Task TriggerTypingAsync(RequestOptions options = null) | |||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public IDisposable EnterTypingState(RequestOptions options = null) | |||
=> ChannelHelper.EnterTypingState(this, Discord, options); | |||
@@ -357,9 +357,12 @@ namespace Discord.WebSocket | |||
=> GuildHelper.DeleteAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyAsync(this, Discord, func, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyEmbedAsync(this, Discord, func, options); | |||
/// <inheritdoc /> | |||
@@ -431,31 +434,37 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
public SocketVoiceChannel GetVoiceChannel(ulong id) | |||
=> GetChannel(id) as SocketVoiceChannel; | |||
/// <summary> | |||
/// Creates a text channel with the provided name. | |||
/// </summary> | |||
/// <param name="name">The name of the new channel.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null" />.</exception> | |||
/// <returns> | |||
/// The created text channel. | |||
/// </returns> | |||
public Task<RestTextChannel> CreateTextChannelAsync(string name, RequestOptions options = null) | |||
=> GuildHelper.CreateTextChannelAsync(this, Discord, name, options); | |||
/// <summary> | |||
/// Creates a voice channel with the provided name. | |||
/// </summary> | |||
/// <param name="name">The name of the new channel.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null" />.</exception> | |||
/// <returns> | |||
/// The created voice channel. | |||
/// </returns> | |||
public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null) | |||
=> GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); | |||
/// <summary> | |||
/// Creates a category channel with the provided name. | |||
/// </summary> | |||
/// <param name="name">The name of the new channel.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null" />.</exception> | |||
/// <returns> | |||
/// The created category channel. | |||
/// </returns> | |||
@@ -507,6 +516,7 @@ namespace Discord.WebSocket | |||
return value; | |||
return null; | |||
} | |||
/// <summary> | |||
/// Creates a role. | |||
/// </summary> | |||
@@ -517,6 +527,7 @@ namespace Discord.WebSocket | |||
/// <param name="color">The color of the role. Set to <see langword="null" /> to use the default color.</param> | |||
/// <param name="isHoisted">Used to determine if users of this role are separated in the user list.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null" />.</exception> | |||
/// <returns> | |||
/// The created role. | |||
/// </returns> | |||
@@ -645,6 +656,7 @@ namespace Discord.WebSocket | |||
public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null) | |||
=> GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); | |||
/// <inheritdoc /> | |||
@@ -931,9 +943,9 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options) | |||
=> await GetWebhookAsync(id, options); | |||
=> await GetWebhookAsync(id, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) | |||
=> await GetWebhooksAsync(options); | |||
=> await GetWebhooksAsync(options).ConfigureAwait(false); | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using Discord.Rest; | |||
using Discord.Rest; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -8,27 +8,38 @@ using Model = Discord.API.Message; | |||
namespace Discord.WebSocket | |||
{ | |||
/// <summary> | |||
/// Represents a WebSocket-based message. | |||
/// </summary> | |||
public abstract class SocketMessage : SocketEntity<ulong>, IMessage | |||
{ | |||
private long _timestampTicks; | |||
public SocketUser Author { get; } | |||
public ISocketMessageChannel Channel { get; } | |||
/// <inheritdoc /> | |||
public MessageSource Source { get; } | |||
/// <inheritdoc /> | |||
public string Content { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
/// <inheritdoc /> | |||
public virtual bool IsTTS => false; | |||
/// <inheritdoc /> | |||
public virtual bool IsPinned => false; | |||
/// <inheritdoc /> | |||
public virtual DateTimeOffset? EditedTimestamp => null; | |||
public virtual IReadOnlyCollection<Attachment> Attachments => ImmutableArray.Create<Attachment>(); | |||
public virtual IReadOnlyCollection<Embed> Embeds => ImmutableArray.Create<Embed>(); | |||
public virtual IReadOnlyCollection<SocketGuildChannel> MentionedChannels => ImmutableArray.Create<SocketGuildChannel>(); | |||
public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | |||
public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | |||
/// <inheritdoc /> | |||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||
@@ -54,6 +65,7 @@ namespace Discord.WebSocket | |||
Content = model.Content.Value; | |||
} | |||
/// <inheritdoc /> | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> MessageHelper.DeleteAsync(this, Discord, options); | |||
@@ -61,13 +73,21 @@ namespace Discord.WebSocket | |||
internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; | |||
//IMessage | |||
/// <inheritdoc /> | |||
IUser IMessage.Author => Author; | |||
/// <inheritdoc /> | |||
IMessageChannel IMessage.Channel => Channel; | |||
/// <inheritdoc /> | |||
MessageType IMessage.Type => MessageType.Default; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using Model = Discord.API.Gateway.Reaction; | |||
using Model = Discord.API.Gateway.Reaction; | |||
namespace Discord.WebSocket | |||
{ | |||
@@ -9,6 +9,7 @@ namespace Discord.WebSocket | |||
public ulong MessageId { get; } | |||
public Optional<SocketUserMessage> Message { get; } | |||
public ISocketMessageChannel Channel { get; } | |||
/// <inheritdoc /> | |||
public IEmote Emote { get; } | |||
internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<IUser> user, IEmote emoji) | |||
@@ -30,6 +31,7 @@ namespace Discord.WebSocket | |||
return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote); | |||
} | |||
/// <inheritdoc /> | |||
public override bool Equals(object other) | |||
{ | |||
if (other == null) return false; | |||
@@ -41,6 +43,7 @@ namespace Discord.WebSocket | |||
return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); | |||
} | |||
/// <inheritdoc /> | |||
public override int GetHashCode() | |||
{ | |||
unchecked | |||
@@ -1,4 +1,4 @@ | |||
using System.Diagnostics; | |||
using System.Diagnostics; | |||
using Model = Discord.API.Message; | |||
namespace Discord.WebSocket | |||
@@ -6,6 +6,7 @@ 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) | |||
@@ -17,24 +17,34 @@ namespace Discord.WebSocket | |||
private ImmutableArray<Attachment> _attachments; | |||
private ImmutableArray<Embed> _embeds; | |||
private ImmutableArray<ITag> _tags; | |||
private List<SocketReaction> _reactions = new List<SocketReaction>(); | |||
private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | |||
/// <inheritdoc /> | |||
public override bool IsTTS => _isTTS; | |||
/// <inheritdoc /> | |||
public override bool IsPinned => _isPinned; | |||
/// <inheritdoc /> | |||
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<Attachment> Attachments => _attachments; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<Embed> Embeds => _embeds; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ITag> Tags => _tags; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketRole> MentionedRoles => MessageHelper.FilterTagsByValue<SocketRole>(TagType.RoleMention, _tags); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags); | |||
/// <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) }); | |||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||
: base(discord, id, channel, author, source) | |||
{ | |||
} | |||
internal static new SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||
internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||
{ | |||
var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | |||
entity.Update(state, model); | |||
@@ -121,30 +131,40 @@ namespace Discord.WebSocket | |||
_reactions.Clear(); | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="InvalidOperationException">Only the author of a message may modify the message.</exception> | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null) | |||
=> MessageHelper.ModifyAsync(this, Discord, func, options); | |||
/// <inheritdoc /> | |||
public Task AddReactionAsync(IEmote emote, RequestOptions options = null) | |||
=> MessageHelper.AddReactionAsync(this, emote, Discord, options); | |||
/// <inheritdoc /> | |||
public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) | |||
=> MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); | |||
/// <inheritdoc /> | |||
public Task RemoveAllReactionsAsync(RequestOptions options = null) | |||
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) | |||
=> MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options); | |||
/// <inheritdoc /> | |||
public Task PinAsync(RequestOptions options = null) | |||
=> MessageHelper.PinAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task UnpinAsync(RequestOptions options = null) | |||
=> MessageHelper.UnpinAsync(this, Discord, 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) | |||
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
/// <inheritdoc /> | |||
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; | |||
internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using Discord.Rest; | |||
using Discord.Rest; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
@@ -13,16 +13,25 @@ namespace Discord.WebSocket | |||
{ | |||
public SocketGuild Guild { get; } | |||
/// <inheritdoc /> | |||
public Color Color { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsHoisted { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsManaged { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsMentionable { get; private set; } | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public GuildPermissions Permissions { get; private set; } | |||
/// <inheritdoc /> | |||
public int Position { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
public bool IsEveryone => Id == Guild.Id; | |||
/// <inheritdoc /> | |||
public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); | |||
public IEnumerable<SocketGuildUser> Members | |||
=> Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); | |||
@@ -49,8 +58,10 @@ namespace Discord.WebSocket | |||
Permissions = new GuildPermissions(model.Permissions); | |||
} | |||
/// <inheritdoc /> | |||
public Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null) | |||
=> RoleHelper.ModifyAsync(this, Discord, func, options); | |||
/// <inheritdoc /> | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> RoleHelper.DeleteAsync(this, Discord, options); | |||
@@ -58,9 +69,11 @@ namespace Discord.WebSocket | |||
private string DebuggerDisplay => $"{Name} ({Id})"; | |||
internal SocketRole Clone() => MemberwiseClone() as SocketRole; | |||
/// <inheritdoc /> | |||
public int CompareTo(IRole role) => RoleUtils.Compare(this, role); | |||
//IRole | |||
/// <inheritdoc /> | |||
IGuild IRole.Guild => Guild; | |||
} | |||
} |
@@ -13,7 +13,7 @@ using PresenceModel = Discord.API.Presence; | |||
namespace Discord.WebSocket | |||
{ | |||
/// <summary> | |||
/// Represents a WebSocket guild user. | |||
/// Represents a WebSocket-based guild user. | |||
/// </summary> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class SocketGuildUser : SocketUser, IGuildUser | |||
@@ -1,13 +1,14 @@ | |||
using System.Diagnostics; | |||
using System.Diagnostics; | |||
using Model = Discord.API.Presence; | |||
namespace Discord.WebSocket | |||
{ | |||
//TODO: C#7 Candidate for record type | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public struct SocketPresence : IPresence | |||
{ | |||
/// <inheritdoc /> | |||
public UserStatus Status { get; } | |||
/// <inheritdoc /> | |||
public IActivity Activity { get; } | |||
internal SocketPresence(UserStatus status, IActivity activity) | |||
@@ -1,4 +1,4 @@ | |||
using Discord.Rest; | |||
using Discord.Rest; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
@@ -9,17 +9,26 @@ namespace Discord.WebSocket | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class SocketSelfUser : SocketUser, ISelfUser | |||
{ | |||
/// <inheritdoc /> | |||
public string Email { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsVerified { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsMfaEnabled { get; private set; } | |||
internal override SocketGlobalUser GlobalUser { get; } | |||
/// <inheritdoc /> | |||
public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } | |||
/// <inheritdoc /> | |||
public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } | |||
/// <inheritdoc /> | |||
public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } | |||
/// <inheritdoc /> | |||
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | |||
/// <inheritdoc /> | |||
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||
/// <inheritdoc /> | |||
public override bool IsWebhook => false; | |||
internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) | |||
@@ -53,7 +62,8 @@ namespace Discord.WebSocket | |||
} | |||
return hasGlobalChanges; | |||
} | |||
/// <inheritdoc /> | |||
public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) | |||
=> UserHelper.ModifyAsync(this, Discord, func, options); | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using Model = Discord.API.User; | |||
@@ -15,7 +15,8 @@ namespace Discord.WebSocket | |||
public override bool IsWebhook => false; | |||
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } | |||
internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } | |||
internal override SocketGlobalUser GlobalUser => | |||
throw new NotSupportedException(); | |||
internal SocketUnknownUser(DiscordSocketClient discord, ulong id) | |||
: base(discord, id) | |||
@@ -1,11 +1,15 @@ | |||
using Discord.Rest; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.User; | |||
namespace Discord.WebSocket | |||
{ | |||
/// <summary> The WebSocket variant of <see cref="IUser"/>. Represents a Discord user. </summary> | |||
/// <summary> | |||
/// Represents a WebSocket-based user. | |||
/// </summary> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public abstract class SocketUser : SocketEntity<ulong>, IUser | |||
{ | |||
/// <inheritdoc /> | |||
@@ -68,7 +72,7 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public async Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) | |||
=> GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; | |||
=> GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false) as IDMChannel; | |||
/// <inheritdoc /> | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
@@ -78,6 +82,12 @@ namespace Discord.WebSocket | |||
public string GetDefaultAvatarUrl() | |||
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); | |||
/// <summary> | |||
/// Gets the full name of the user (e.g. Example#0001). | |||
/// </summary> | |||
/// <returns> | |||
/// The full name of the user. | |||
/// </returns> | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
internal SocketUser Clone() => MemberwiseClone() as SocketUser; | |||
@@ -1,10 +1,9 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using Model = Discord.API.VoiceState; | |||
namespace Discord.WebSocket | |||
{ | |||
//TODO: C#7 Candidate for record type | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public struct SocketVoiceState : IVoiceState | |||
{ | |||
@@ -22,14 +21,23 @@ namespace Discord.WebSocket | |||
} | |||
private readonly Flags _voiceStates; | |||
/// <summary> | |||
/// Gets the voice channel that the user is currently in; or <see langword="null"/> if none. | |||
/// </summary> | |||
public SocketVoiceChannel VoiceChannel { get; } | |||
/// <inheritdoc /> | |||
public string VoiceSessionId { get; } | |||
/// <inheritdoc /> | |||
public bool IsMuted => (_voiceStates & Flags.Muted) != 0; | |||
/// <inheritdoc /> | |||
public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0; | |||
/// <inheritdoc /> | |||
public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; | |||
/// <inheritdoc /> | |||
public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; | |||
/// <inheritdoc /> | |||
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; | |||
internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed) | |||
@@ -55,6 +63,12 @@ namespace Discord.WebSocket | |||
return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress); | |||
} | |||
/// <summary> | |||
/// Gets the name of the voice channel. | |||
/// </summary> | |||
/// <returns> | |||
/// The name of the voice channel. | |||
/// </returns> | |||
public override string ToString() => VoiceChannel?.Name ?? "Unknown"; | |||
private string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})"; | |||
internal SocketVoiceState Clone() => this; | |||
@@ -63,26 +63,32 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Webhook users cannot be kicked.</exception> | |||
Task IGuildUser.KickAsync(string reason, RequestOptions options) => | |||
throw new NotSupportedException("Webhook users cannot be kicked."); | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Webhook users cannot be modified.</exception> | |||
Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) => | |||
throw new NotSupportedException("Webhook users cannot be modified."); | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> | |||
Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> | |||
Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> | |||
Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> | |||
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
@@ -1,4 +1,4 @@ | |||
#if DEFAULTWEBSOCKET | |||
#if DEFAULTWEBSOCKET | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
@@ -248,4 +248,4 @@ namespace Discord.Net.WebSockets | |||
} | |||
} | |||
} | |||
#endif | |||
#endif |
@@ -8,6 +8,7 @@ namespace Discord.Net.WebSockets | |||
#if DEFAULTWEBSOCKET | |||
public static readonly WebSocketProvider Instance = Create(); | |||
/// <exception cref="PlatformNotSupportedException">The default WebSocketProvider is not supported on this platform.</exception> | |||
public static WebSocketProvider Create(IWebProxy proxy = null) | |||
{ | |||
return () => | |||
@@ -30,4 +31,4 @@ namespace Discord.Net.WebSockets | |||
}; | |||
#endif | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
@@ -12,11 +12,12 @@ namespace Discord.Webhook | |||
{ | |||
internal static class WebhookClientHelper | |||
{ | |||
/// <exception cref="InvalidOperationException">Could not find a webhook with the supplied credentials.</exception> | |||
public static async Task<RestInternalWebhook> GetWebhookAsync(DiscordWebhookClient client, ulong webhookId) | |||
{ | |||
var model = await client.ApiClient.GetWebhookAsync(webhookId).ConfigureAwait(false); | |||
if (model == null) | |||
throw new InvalidOperationException("Could not find a webhook for the supplied credentials."); | |||
throw new InvalidOperationException("Could not find a webhook with the supplied credentials."); | |||
return RestInternalWebhook.Create(client, model); | |||
} | |||
public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client, | |||