| @@ -37,6 +37,7 @@ | |||||
| "default", | "default", | ||||
| "_template/light-dark-theme" | "_template/light-dark-theme" | ||||
| ], | ], | ||||
| "postProcessors": [ "ExtractSearchIndex" ], | |||||
| "overwrite": "_overwrites/**/**.md", | "overwrite": "_overwrites/**/**.md", | ||||
| "globalMetadata": { | "globalMetadata": { | ||||
| "_appTitle": "Discord.Net Documentation", | "_appTitle": "Discord.Net Documentation", | ||||
| @@ -19,6 +19,7 @@ namespace Discord.Commands | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| /// <param name="overridenTypeReader">The <see cref="TypeReader"/> to be used with the parameter. </param> | /// <param name="overridenTypeReader">The <see cref="TypeReader"/> to be used with the parameter. </param> | ||||
| /// <exception cref="ArgumentException">The given <paramref name="overridenTypeReader"/> does not inherit from <see cref="TypeReader"/>.</exception> | |||||
| public OverrideTypeReaderAttribute(Type overridenTypeReader) | public OverrideTypeReaderAttribute(Type overridenTypeReader) | ||||
| { | { | ||||
| if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||||
| /// <remarks> | /// <remarks> | ||||
| /// <see cref="Preconditions" /> of the same group require only one of the preconditions to pass in order to | /// <see cref="Preconditions" /> of the same group require only one of the preconditions to pass in order to | ||||
| /// be successful (A || B). Specifying <see cref="Group" /> = <see langword="null" /> or not at all will | /// be successful (A || B). Specifying <see cref="Group" /> = <see langword="null" /> or not at all will | ||||
| /// require *all* preconditions to pass, just like normal (A && B). | |||||
| /// require *all* preconditions to pass, just like normal (A && B). | |||||
| /// </remarks> | /// </remarks> | ||||
| public string Group { get; set; } = null; | public string Group { get; set; } = null; | ||||
| @@ -16,7 +16,12 @@ namespace Discord.Commands | |||||
| /// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary> | /// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary> | ||||
| public bool IsPrivate => Channel is IPrivateChannel; | public bool IsPrivate => Channel is IPrivateChannel; | ||||
| /// <summary> | |||||
| /// Initializes a new <see cref="CommandContext" /> class with the provided client and message. | |||||
| /// </summary> | |||||
| /// <param name="client">The underlying client.</param> | |||||
| /// <param name="msg">The underlying message.</param> | |||||
| public CommandContext(IDiscordClient client, IUserMessage msg) | public CommandContext(IDiscordClient client, IUserMessage msg) | ||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| @@ -13,11 +13,14 @@ namespace Discord.Commands | |||||
| { | { | ||||
| public class CommandService | public class CommandService | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Occurs when a command-related information is received. | |||||
| /// </summary> | |||||
| public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | ||||
| internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | ||||
| /// <summary> | /// <summary> | ||||
| /// Fired when a command is successfully executed without any runtime error. | |||||
| /// Occurs when a command is successfully executed without any runtime error. | |||||
| /// </summary> | /// </summary> | ||||
| public event Func<CommandInfo, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } | public event Func<CommandInfo, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } | ||||
| internal readonly AsyncEvent<Func<CommandInfo, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent<Func<CommandInfo, ICommandContext, IResult, Task>>(); | internal readonly AsyncEvent<Func<CommandInfo, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent<Func<CommandInfo, ICommandContext, IResult, Task>>(); | ||||
| @@ -51,7 +54,16 @@ namespace Discord.Commands | |||||
| /// </summary> | /// </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); | public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); | ||||
| /// <summary> | |||||
| /// Initializes a new <see cref="CommandService"/> class. | |||||
| /// </summary> | |||||
| public CommandService() : this(new CommandServiceConfig()) { } | public CommandService() : this(new CommandServiceConfig()) { } | ||||
| /// <summary> | |||||
| /// Initializes a new <see cref="CommandService" /> class with the provided configuration. | |||||
| /// </summary> | |||||
| /// <param name="config">The configuration class.</param> | |||||
| /// <exception cref="InvalidOperationException">The <see cref="RunMode"/> is set to <see cref="RunMode.Default"/>.</exception> | |||||
| public CommandService(CommandServiceConfig config) | public CommandService(CommandServiceConfig config) | ||||
| { | { | ||||
| _caseSensitive = config.CaseSensitiveCommands; | _caseSensitive = config.CaseSensitiveCommands; | ||||
| @@ -121,6 +133,7 @@ namespace Discord.Commands | |||||
| /// A built module. | /// A built module. | ||||
| /// </returns> | /// </returns> | ||||
| public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services); | public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services); | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds a command module from a <see cref="Type" /> . | /// Adds a command module from a <see cref="Type" /> . | ||||
| /// </summary> | /// </summary> | ||||
| @@ -132,6 +145,8 @@ namespace Discord.Commands | |||||
| /// <returns> | /// <returns> | ||||
| /// A built module. | /// A built module. | ||||
| /// </returns> | /// </returns> | ||||
| /// <exception cref="ArgumentException">This module has already been added.</exception> | |||||
| /// <exception cref="InvalidOperationException">The <see cref="ModuleInfo"/> fails to be built; an invalid type may have been provided.</exception> | |||||
| public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services) | public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services) | ||||
| { | { | ||||
| services = services ?? EmptyServiceProvider.Instance; | services = services ?? EmptyServiceProvider.Instance; | ||||
| @@ -209,7 +224,7 @@ namespace Discord.Commands | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="module">The <see cref="ModuleInfo" /> to be removed from the service.</param> | /// <param name="module">The <see cref="ModuleInfo" /> to be removed from the service.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// Returns whether the <paramref name="module"/> is successfully removed. | |||||
| /// Returns whether the module is successfully removed. | |||||
| /// </returns> | /// </returns> | ||||
| public async Task<bool> RemoveModuleAsync(ModuleInfo module) | public async Task<bool> RemoveModuleAsync(ModuleInfo module) | ||||
| { | { | ||||
| @@ -223,7 +238,21 @@ namespace Discord.Commands | |||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Removes the command module. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The <see cref="Type"/> of the module.</typeparam> | |||||
| /// <returns> | |||||
| /// Returns whether the module is successfully removed. | |||||
| /// </returns> | |||||
| public Task<bool> RemoveModuleAsync<T>() => RemoveModuleAsync(typeof(T)); | public Task<bool> RemoveModuleAsync<T>() => RemoveModuleAsync(typeof(T)); | ||||
| /// <summary> | |||||
| /// Removes the command module. | |||||
| /// </summary> | |||||
| /// <param name="type">The <see cref="Type"/> of the module.</param> | |||||
| /// <returns> | |||||
| /// Returns whether the module is successfully removed. | |||||
| /// </returns> | |||||
| public async Task<bool> RemoveModuleAsync(Type type) | public async Task<bool> RemoveModuleAsync(Type type) | ||||
| { | { | ||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -2,23 +2,40 @@ using System; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a configuration class for <see cref="CommandService" />. | |||||
| /// </summary> | |||||
| public class CommandServiceConfig | public class CommandServiceConfig | ||||
| { | { | ||||
| /// <summary> Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the default <see cref="RunMode" /> commands should have, if one is not specified on the | |||||
| /// Command attribute or builder. | |||||
| /// </summary> | |||||
| public RunMode DefaultRunMode { get; set; } = RunMode.Sync; | public RunMode DefaultRunMode { get; set; } = RunMode.Sync; | ||||
| /// <summary> | |||||
| /// Gets or sets the <see cref="char"/> that separates an argument with another. | |||||
| /// </summary> | |||||
| public char SeparatorChar { get; set; } = ' '; | public char SeparatorChar { get; set; } = ' '; | ||||
| /// <summary> Determines whether commands should be case-sensitive. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets whether commands should be case-sensitive. | |||||
| /// </summary> | |||||
| public bool CaseSensitiveCommands { get; set; } = false; | public bool CaseSensitiveCommands { get; set; } = false; | ||||
| /// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the minimum log level severity that will be sent to the <see cref="CommandService.Log"/> event. | |||||
| /// </summary> | |||||
| public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | ||||
| /// <summary> Determines whether RunMode.Sync commands should push exceptions up to the caller. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets whether <see cref="RunMode.Sync"/> commands should push exceptions up to the caller. | |||||
| /// </summary> | |||||
| public bool ThrowOnError { get; set; } = true; | public bool ThrowOnError { get; set; } = true; | ||||
| /// <summary> Determines whether extra parameters should be ignored. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets whether extra parameters should be ignored. | |||||
| /// </summary> | |||||
| public bool IgnoreExtraArgs { get; set; } = false; | public bool IgnoreExtraArgs { get; set; } = false; | ||||
| ///// <summary> Gets or sets the <see cref="IServiceProvider"/> to use. </summary> | ///// <summary> Gets or sets the <see cref="IServiceProvider"/> to use. </summary> | ||||
| @@ -9,32 +9,45 @@ namespace Discord.Commands | |||||
| public abstract class ModuleBase<T> : IModuleBase | public abstract class ModuleBase<T> : IModuleBase | ||||
| where T : class, ICommandContext | where T : class, ICommandContext | ||||
| { | { | ||||
| /// <summary> | |||||
| /// The underlying context of the command. | |||||
| /// </summary> | |||||
| /// <seealso cref="T:Discord.Commands.ICommandContext" /> | |||||
| /// <seealso cref="T:Discord.Commands.CommandContext" /> | |||||
| public T Context { get; private set; } | public T Context { get; private set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sends a message to the source channel | |||||
| /// Sends a message to the source channel. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="message">Contents of the message; optional only if <paramref name="embed"/> is specified</param> | |||||
| /// <param name="isTTS">Specifies if Discord should read this message aloud using TTS</param> | |||||
| /// <param name="embed">An embed to be displayed alongside the message</param> | |||||
| /// <param name="message"> | |||||
| /// Contents of the message; optional only if <paramref name="embed" /> is specified. | |||||
| /// </param> | |||||
| /// <param name="isTTS">Specifies if Discord should read this <paramref name="message"/> aloud using text-to-speech.</param> | |||||
| /// <param name="embed">An embed to be displayed alongside the <paramref name="message"/>.</param> | |||||
| protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| { | { | ||||
| return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); | return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// The method to execute before executing the command. | |||||
| /// The method to execute before executing the command. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||||
| protected virtual void BeforeExecute(CommandInfo command) | protected virtual void BeforeExecute(CommandInfo command) | ||||
| { | { | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// The method to execute after executing the command. | |||||
| /// The method to execute after executing the command. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="command"></param> | |||||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||||
| protected virtual void AfterExecute(CommandInfo command) | protected virtual void AfterExecute(CommandInfo command) | ||||
| { | { | ||||
| } | } | ||||
| /// <summary> | |||||
| /// The method to execute when building the module. | |||||
| /// </summary> | |||||
| /// <param name="commandService">The <see cref="CommandService"/> used to create the module.</param> | |||||
| /// <param name="builder">The builder used to build the module.</param> | |||||
| protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder) | protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder) | ||||
| { | { | ||||
| } | } | ||||
| @@ -1,5 +1,8 @@ | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Specifies the behavior when multiple matches are found during the command parsing stage. | |||||
| /// </summary> | |||||
| public enum MultiMatchHandling | public enum MultiMatchHandling | ||||
| { | { | ||||
| /// <summary> Indicates that when multiple results are found, an exception should be thrown. </summary> | /// <summary> Indicates that when multiple results are found, an exception should be thrown. </summary> | ||||
| @@ -1,5 +1,10 @@ | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Specifies the behavior of the command execution workflow. | |||||
| /// </summary> | |||||
| /// <seealso cref="CommandServiceConfig"/> | |||||
| /// <seealso cref="CommandAttribute"/> | |||||
| public enum RunMode | public enum RunMode | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| @@ -20,16 +20,13 @@ namespace Discord | |||||
| /// When modifying an <see cref="ITextChannel" />, the <see cref="Name" /> | /// When modifying an <see cref="ITextChannel" />, the <see cref="Name" /> | ||||
| /// MUST be alphanumeric with dashes. It must match the following RegEx: [a-z0-9-_]{2,100} | /// MUST be alphanumeric with dashes. It must match the following RegEx: [a-z0-9-_]{2,100} | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <exception cref="Discord.Net.HttpException"> | |||||
| /// A BadRequest will be thrown if the name does not match the above RegEx. | |||||
| /// </exception> | |||||
| public Optional<string> Name { get; set; } | public Optional<string> Name { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Moves the channel to the following position. This is 0-based! | |||||
| /// Moves the channel to the following position. This property is zero-based. | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<int> Position { get; set; } | public Optional<int> Position { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the category for this channel. | |||||
| /// Gets or sets the category ID for this channel. | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<ulong?> CategoryId { get; set; } | public Optional<ulong?> CategoryId { get; set; } | ||||
| } | } | ||||
| @@ -4,7 +4,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a generic Discord channel. | |||||
| /// Represents a generic channel. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IChannel : ISnowflakeEntity | public interface IChannel : ISnowflakeEntity | ||||
| { | { | ||||
| @@ -3,7 +3,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a generic DM channel. | |||||
| /// Represents a generic direct-message channel. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IDMChannel : IMessageChannel, IPrivateChannel | public interface IDMChannel : IMessageChannel, IPrivateChannel | ||||
| { | { | ||||
| @@ -15,6 +15,7 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Closes this private channel, removing it from your channel list. | /// Closes this private channel, removing it from your channel list. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task CloseAsync(RequestOptions options = null); | Task CloseAsync(RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,13 +3,14 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a private generic group channel. | |||||
| /// Represents a generic private group channel. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel | public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Leaves this group. | /// Leaves this group. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task LeaveAsync(RequestOptions options = null); | Task LeaveAsync(RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -5,34 +5,52 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a guild channel (text, voice, category). | |||||
| /// Represents a generic guild channel. | |||||
| /// </summary> | /// </summary> | ||||
| /// <seealso cref="ITextChannel"/> | |||||
| /// <seealso cref="IVoiceChannel"/> | |||||
| /// <seealso cref="ICategoryChannel"/> | |||||
| public interface IGuildChannel : IChannel, IDeletable | public interface IGuildChannel : IChannel, IDeletable | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the position of this channel in the guild's channel list, relative to others of the same type. | |||||
| /// Gets the position of this channel. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The position of this channel in the guild's channel list, relative to others of the same type. | |||||
| /// </returns> | |||||
| int Position { get; } | int Position { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the parent ID (category) of this channel in the guild's channel list. | /// Gets the parent ID (category) of this channel in the guild's channel list. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The parent category ID associated with this channel, or <see langword="null"/> if none is set. | |||||
| /// </returns> | |||||
| ulong? CategoryId { get; } | ulong? CategoryId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the parent channel (category) of this channel. | /// Gets the parent channel (category) of this channel. | ||||
| /// </summary> | /// </summary> | ||||
| Task<ICategoryChannel> GetCategoryAsync(); | Task<ICategoryChannel> GetCategoryAsync(); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the guild this channel is a member of. | |||||
| /// Gets the guild associated with this channel. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The guild that this channel belongs to. | |||||
| /// </returns> | |||||
| IGuild Guild { get; } | IGuild Guild { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the id of the guild this channel is a member of. | |||||
| /// Gets the guild ID associated with this channel. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The guild ID that this channel belongs to. | |||||
| /// </returns> | |||||
| ulong GuildId { get; } | ulong GuildId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of permission overwrites for this channel. | /// Gets a collection of permission overwrites for this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// A collection of overwrites associated with this channel. | |||||
| /// </returns> | |||||
| IReadOnlyCollection<Overwrite> PermissionOverwrites { get; } | IReadOnlyCollection<Overwrite> PermissionOverwrites { get; } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -50,49 +68,77 @@ namespace Discord | |||||
| /// <param name="isUnique"> | /// <param name="isUnique"> | ||||
| /// If <see langword="true"/>, don't try to reuse a similar invite (useful for creating many unique one time use invites). | /// If <see langword="true"/>, don't try to reuse a similar invite (useful for creating many unique one time use invites). | ||||
| /// </param> | /// </param> | ||||
| /// <param name="options"> | |||||
| /// The options to be used when sending the request. | |||||
| /// </param> | |||||
| Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); | Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns a collection of all invites to this channel. | /// Returns a collection of all invites to this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this guild channel. | /// Modifies this guild channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyAsync(Action<GuildChannelProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<GuildChannelProperties> func, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the permission overwrite for a specific role, or <see langword="null"/> if one does not exist. | /// Gets the permission overwrite for a specific role, or <see langword="null"/> if one does not exist. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="role">The role to get the overwrite from.</param> | |||||
| OverwritePermissions? GetPermissionOverwrite(IRole role); | OverwritePermissions? GetPermissionOverwrite(IRole role); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the permission overwrite for a specific user, or <see langword="null"/> if one does not exist. | /// Gets the permission overwrite for a specific user, or <see langword="null"/> if one does not exist. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="user">The user to get the overwrite from.</param> | |||||
| OverwritePermissions? GetPermissionOverwrite(IUser user); | OverwritePermissions? GetPermissionOverwrite(IUser user); | ||||
| /// <summary> | /// <summary> | ||||
| /// Removes the permission overwrite for the given role, if one exists. | /// Removes the permission overwrite for the given role, if one exists. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="role">The role to remove the overwrite from.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null); | Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Removes the permission overwrite for the given user, if one exists. | /// Removes the permission overwrite for the given user, if one exists. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="user">The user to remove the overwrite from.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null); | Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds or updates the permission overwrite for the given role. | /// Adds or updates the permission overwrite for the given role. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="role">The role to add the overwrite to.</param> | |||||
| /// <param name="permissions">The overwrite to add to the role.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null); | Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds or updates the permission overwrite for the given user. | /// Adds or updates the permission overwrite for the given user. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="user">The user to add the overwrite to.</param> | |||||
| /// <param name="permissions">The overwrite to add to the user.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null); | Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all users in this channel. | /// Gets a collection of all users in this channel. | ||||
| /// </summary> | /// </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> | |||||
| new IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | new IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a user in this channel with the provided ID. | /// Gets a user in this channel with the provided ID. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The ID of the user.</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> | |||||
| new Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | new Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -13,50 +13,115 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Sends a message to this message channel. | /// Sends a message to this message channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); | Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); | ||||
| #if FILESYSTEM | #if FILESYSTEM | ||||
| /// <summary> | /// <summary> | ||||
| /// Sends a file to this message channel, with an optional caption. | /// Sends a file to this message channel, with an optional caption. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="filePath">The file path of the file.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <remarks> | |||||
| /// If you wish to upload an image and have it embedded in a <see cref="EmbedType.Rich"/> embed, you may | |||||
| /// upload the file and refer to the file with "attachment://filename.ext" in the | |||||
| /// <see cref="Discord.EmbedBuilder.ImageUrl"/>. | |||||
| /// </remarks> | |||||
| Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | ||||
| #endif | #endif | ||||
| /// <summary> | /// <summary> | ||||
| /// Sends a file to this message channel, with an optional caption. | /// Sends a file to this message channel, with an optional caption. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="stream">The <see cref="Stream"/> of the file to be sent.</param> | |||||
| /// <param name="filename">The name of the attachment.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <remarks> | |||||
| /// If you wish to upload an image and have it embedded in a <see cref="EmbedType.Rich"/> embed, you may | |||||
| /// upload the file and refer to the file with "attachment://filename.ext" in the | |||||
| /// <see cref="Discord.EmbedBuilder.ImageUrl"/>. | |||||
| /// </remarks> | |||||
| Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a message from this message channel with the given id, or <see langword="null"/> if not found. | /// Gets a message from this message channel with the given id, or <see langword="null"/> if not found. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The ID of the message.</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> | |||||
| /// The message gotten from either the cache or the download, or <see langword="null"/> if none is found. | |||||
| /// </returns> | |||||
| Task<IMessage> GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IMessage> GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the last N messages from this message channel. | /// Gets the last N messages from this message channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="limit">The numbers of message to be gotten from.</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> | |||||
| /// Paged collection of messages. Flattening the paginated response into a collection of messages with | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> is required if you wish to access the messages. | |||||
| /// </returns> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, | IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, | ||||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of messages in this channel. | /// Gets a collection of messages in this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="fromMessageId">The ID of the starting message to get the messages from.</param> | |||||
| /// <param name="dir">The direction of the messages to be gotten from.</param> | |||||
| /// <param name="limit">The numbers of message to be gotten from.</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> | |||||
| /// Paged collection of messages. Flattening the paginated response into a collection of messages with | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> is required if you wish to access the messages. | |||||
| /// </returns> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, | IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, | ||||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of messages in this channel. | /// Gets a collection of messages in this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="fromMessage">The starting message to get the messages from.</param> | |||||
| /// <param name="dir">The direction of the messages to be gotten from.</param> | |||||
| /// <param name="limit">The numbers of message to be gotten from.</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> | |||||
| /// Paged collection of messages. Flattening the paginated response into a collection of messages with | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> is required if you wish to access the messages. | |||||
| /// </returns> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, | IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, | ||||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of pinned messages in this channel. | /// Gets a collection of pinned messages in this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of messages. | |||||
| /// </returns> | |||||
| Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. | /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task TriggerTypingAsync(RequestOptions options = null); | Task TriggerTypingAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned | /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned | ||||
| /// object is disposed. | /// object is disposed. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| IDisposable EnterTypingState(RequestOptions options = null); | IDisposable EnterTypingState(RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -8,8 +8,11 @@ namespace Discord | |||||
| public interface IPrivateChannel : IChannel | public interface IPrivateChannel : IChannel | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Users that can access this channel. | |||||
| /// Gets the users that can access this channel. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// A collection of users that can access this channel. | |||||
| /// </returns> | |||||
| IReadOnlyCollection<IUser> Recipients { get; } | IReadOnlyCollection<IUser> Recipients { get; } | ||||
| } | } | ||||
| } | } | ||||
| @@ -11,40 +11,67 @@ namespace Discord | |||||
| public interface ITextChannel : IMessageChannel, IMentionable, IGuildChannel | public interface ITextChannel : IMessageChannel, IMentionable, IGuildChannel | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets whether the channel is NSFW. | |||||
| /// Determines whether the channel is NSFW. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// <see langword="true"/> if the channel has the NSFW flag enabled; otherwise, <see langword="false"/>. | |||||
| /// </returns> | |||||
| bool IsNsfw { get; } | bool IsNsfw { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the current topic for this text channel. | /// Gets the current topic for this text channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The topic set in the channel, or <see langword="null"/> if none is set. | |||||
| /// </returns> | |||||
| string Topic { get; } | string Topic { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Bulk deletes multiple messages. | |||||
| /// Bulk-deletes multiple messages. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="messages">The messages to be bulk-deleted.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null); | Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Bulk deletes multiple messages. | |||||
| /// Bulk-deletes multiple messages. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="messageIds">The IDs of the messages to be bulk-deleted.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null); | Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this text channel. | /// Modifies this text channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Creates a webhook in this text channel. | /// Creates a webhook in this text channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="name">The name of the webhook.</param> | |||||
| /// <param name="avatar">The avatar of the webhook.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// The created webhook. | |||||
| /// </returns> | |||||
| Task<IWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null); | Task<IWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the webhook in this text channel with the provided ID, or <see langword="null"/> if not found. | |||||
| /// Gets the webhook in this text channel with the provided ID. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The ID of the webhook.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A webhook associated with the <paramref name="id"/>, or <see langword="null"/> if not found. | |||||
| /// </returns> | |||||
| Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); | Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the webhooks for this text channel. | /// Gets the webhooks for this text channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of webhooks. | |||||
| /// </returns> | |||||
| Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -21,6 +21,8 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this voice channel. | /// Modifies this voice channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -6,10 +6,8 @@ namespace Discord | |||||
| public class Emoji : IEmote | public class Emoji : IEmote | ||||
| { | { | ||||
| // TODO: need to constrain this to Unicode-only emojis somehow | // TODO: need to constrain this to Unicode-only emojis somehow | ||||
| /// <summary> | |||||
| /// Gets the Unicode representation of this emote. | |||||
| /// </summary> | |||||
| /// <inheritdoc /> | |||||
| public string Name { get; } | public string Name { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the Unicode representation of this emote. | /// Gets the Unicode representation of this emote. | ||||
| @@ -28,7 +26,7 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Determines whether the specified emoji is equal to the current emoji. | /// Determines whether the specified emoji is equal to the current emoji. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="obj">The object to compare with the current object.</param> | |||||
| /// <param name="other">The object to compare with the current object.</param> | |||||
| public override bool Equals(object other) | public override bool Equals(object other) | ||||
| { | { | ||||
| if (other == null) return false; | if (other == null) return false; | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Diagnostics; | |||||
| using System.Globalization; | using System.Globalization; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -6,15 +7,12 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// A custom image-based emote. | /// A custom image-based emote. | ||||
| /// </summary> | /// </summary> | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class Emote : IEmote, ISnowflakeEntity | public class Emote : IEmote, ISnowflakeEntity | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Gets the display name (tooltip) of this emote. | |||||
| /// </summary> | |||||
| /// <inheritdoc /> | |||||
| public string Name { get; } | public string Name { get; } | ||||
| /// <summary> | |||||
| /// Gets the ID of this emote. | |||||
| /// </summary> | |||||
| /// <inheritdoc /> | |||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets whether this emote is animated. | /// Gets whether this emote is animated. | ||||
| @@ -36,6 +34,10 @@ namespace Discord | |||||
| Animated = animated; | Animated = animated; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Determines whether the specified emote is equal to the current emote. | |||||
| /// </summary> | |||||
| /// <param name="other">The object to compare with the current object.</param> | |||||
| public override bool Equals(object other) | public override bool Equals(object other) | ||||
| { | { | ||||
| if (other == null) return false; | if (other == null) return false; | ||||
| @@ -47,6 +49,7 @@ namespace Discord | |||||
| return string.Equals(Name, otherEmote.Name) && Id == otherEmote.Id; | return string.Equals(Name, otherEmote.Name) && Id == otherEmote.Id; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public override int GetHashCode() | public override int GetHashCode() | ||||
| { | { | ||||
| unchecked | unchecked | ||||
| @@ -57,7 +60,8 @@ namespace Discord | |||||
| /// <summary> Parses an <see cref="Emote"/> from its raw format. </summary> | /// <summary> Parses an <see cref="Emote"/> from its raw format. </summary> | ||||
| /// <param name="text">The raw encoding of an emote; for example, <:dab:277855270321782784>.</param> | /// <param name="text">The raw encoding of an emote; for example, <:dab:277855270321782784>.</param> | ||||
| /// <returns>An emote</returns> | |||||
| /// <returns>An emote.</returns> | |||||
| /// <exception cref="ArgumentException">Invalid emote format.</exception> | |||||
| public static Emote Parse(string text) | public static Emote Parse(string text) | ||||
| { | { | ||||
| if (TryParse(text, out Emote result)) | if (TryParse(text, out Emote result)) | ||||
| @@ -65,6 +69,9 @@ namespace Discord | |||||
| throw new ArgumentException("Invalid emote format.", nameof(text)); | throw new ArgumentException("Invalid emote format.", nameof(text)); | ||||
| } | } | ||||
| /// <summary> Tries to parse an <see cref="Emote"/> from its raw format. </summary> | |||||
| /// <param name="text">The raw encoding of an emote; for example, <:dab:277855270321782784>.</param> | |||||
| /// <param name="result">An emote.</param> | |||||
| public static bool TryParse(string text, out Emote result) | public static bool TryParse(string text, out Emote result) | ||||
| { | { | ||||
| result = null; | result = null; | ||||
| @@ -89,6 +96,9 @@ namespace Discord | |||||
| } | } | ||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
| /// <summary> | |||||
| /// Returns the raw representation of the emote. | |||||
| /// </summary> | |||||
| public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; | public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; | ||||
| } | } | ||||
| } | } | ||||
| @@ -31,7 +31,7 @@ namespace Discord | |||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the raw representation of the emoji. | |||||
| /// Gets the raw representation of the emote. | |||||
| /// </summary> | /// </summary> | ||||
| public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; | public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; | ||||
| } | } | ||||
| @@ -8,9 +8,8 @@ namespace Discord | |||||
| /// await Context.Guild.ModifyAsync(async x => | /// await Context.Guild.ModifyAsync(async x => | ||||
| /// { | /// { | ||||
| /// x.Name = "aaaaaah"; | /// x.Name = "aaaaaah"; | ||||
| /// x.RegionId = (await Context.Client.GetOptimalVoiceRegionAsync()).Id; | |||||
| /// }); | /// }); | ||||
| /// </code> | |||||
| /// </code> | |||||
| /// </example> | /// </example> | ||||
| /// <see cref="T:Discord.IGuild" /> | /// <see cref="T:Discord.IGuild" /> | ||||
| public class GuildProperties | public class GuildProperties | ||||
| @@ -61,11 +60,11 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<ulong?> AfkChannelId { get; set; } | public Optional<ulong?> AfkChannelId { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the <see cref="ITextChannel" /> where System messages should be sent. | |||||
| /// Gets or sets the <see cref="ITextChannel" /> where system messages should be sent. | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<ITextChannel> SystemChannel { get; set; } | public Optional<ITextChannel> SystemChannel { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the ID of the <see cref="ITextChannel" /> where System messages should be sent. | |||||
| /// Gets or sets the ID of the <see cref="ITextChannel" /> where system messages should be sent. | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<ulong?> SystemChannelId { get; set; } | public Optional<ulong?> SystemChannelId { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -20,8 +20,11 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| int AFKTimeout { get; } | int AFKTimeout { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns <see langword="true"/> if this guild is embeddable (e.g. widget). | |||||
| /// Determines if this guild is embeddable (i.e. can use widget). | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// Returns <see langword="true"/> if this guild can be embedded via widgets. | |||||
| /// </returns> | |||||
| bool IsEmbeddable { get; } | bool IsEmbeddable { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the default message notifications for users who haven't explicitly set their notification settings. | /// Gets the default message notifications for users who haven't explicitly set their notification settings. | ||||
| @@ -37,29 +40,32 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| VerificationLevel VerificationLevel { get; } | VerificationLevel VerificationLevel { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns the ID of this guild's icon, or <see langword="null"/> if one is not set. | |||||
| /// Returns the ID of this guild's icon, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| string IconId { get; } | string IconId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns the URL of this guild's icon, or <see langword="null"/> if one is not set. | |||||
| /// Returns the URL of this guild's icon, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| string IconUrl { get; } | string IconUrl { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns the ID of this guild's splash image, or <see langword="null"/> if one is not set. | |||||
| /// Returns the ID of this guild's splash image, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| string SplashId { get; } | string SplashId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns the URL of this guild's splash image, or <see langword="null"/> if one is not set. | |||||
| /// Returns the URL of this guild's splash image, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| string SplashUrl { get; } | string SplashUrl { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Determines if this guild is currently connected and ready to be used. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// Returns <see langword="true"/> if this guild is currently connected and ready to be used. Only applies | /// Returns <see langword="true"/> if this guild is currently connected and ready to be used. Only applies | ||||
| /// to the WebSocket client. | /// to the WebSocket client. | ||||
| /// </summary> | |||||
| /// </returns> | |||||
| bool Available { get; } | bool Available { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the ID of the AFK voice channel for this guild if set, or <see langword="null"/> if not. | |||||
| /// Gets the ID of the AFK voice channel for this guild, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| ulong? AFKChannelId { get; } | ulong? AFKChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -67,11 +73,11 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| ulong DefaultChannelId { get; } | ulong DefaultChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the ID of the embed channel for this guild if set, or <see langword="null"/> if not. | |||||
| /// Gets the ID of the embed channel set in the widget settings of this guild, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| ulong? EmbedChannelId { get; } | ulong? EmbedChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the ID of the channel where randomized welcome messages are sent if set, or <see langword="null"/> if not. | |||||
| /// Gets the ID of the channel where randomized welcome messages are sent, or <see langword="null"/> if none is set. | |||||
| /// </summary> | /// </summary> | ||||
| ulong? SystemChannelId { get; } | ulong? SystemChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -106,57 +112,62 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this guild. | /// Modifies this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the guild with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this guild's embed channel. | /// Modifies this guild's embed channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the guild widget with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); | Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Bulk modifies the order of channels in this guild. | /// Bulk modifies the order of channels in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="args">The properties to modify the channel positions with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null); | Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Bulk modifies the order of roles in this guild. | /// Bulk modifies the order of roles in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="args">The properties 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); | Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Leaves this guild. If you are the owner, use | |||||
| /// <see cref="IDeletable.DeleteAsync(Discord.RequestOptions)" /> instead. | |||||
| /// Leaves this guild. If you are the owner, use <see cref="IDeletable.DeleteAsync" /> instead. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task LeaveAsync(RequestOptions options = null); | Task LeaveAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all users banned on this guild. | /// Gets a collection of all users banned on this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Bans the provided <paramref name="user"/> from this guild and optionally prunes their recent messages. | |||||
| /// Bans the provided user from this guild and optionally prunes their recent messages. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="user"> | |||||
| /// The user to ban. | |||||
| /// </param> | |||||
| /// <param name="user">The user to ban.</param> | |||||
| /// <param name="pruneDays"> | /// <param name="pruneDays"> | ||||
| /// The number of days to remove messages from this <paramref name="user"/> for - must be between [0, 7] | |||||
| /// </param> | |||||
| /// <param name="reason"> | |||||
| /// The reason of the ban to be written in the audit log. | |||||
| /// The number of days to remove messages from this <paramref name="user" /> for - must be between [0, 7]. | |||||
| /// </param> | /// </param> | ||||
| /// <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> | |||||
| Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); | Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Bans the provided user ID from this guild and optionally prunes their recent messages. | /// Bans the provided user ID from this guild and optionally prunes their recent messages. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="userId"> | |||||
| /// The ID of the user to ban. | |||||
| /// </param> | |||||
| /// <param name="userId">The ID of the user to ban.</param> | |||||
| /// <param name="pruneDays"> | /// <param name="pruneDays"> | ||||
| /// The number of days to remove messages from this user for - must be between [0, 7] | |||||
| /// </param> | |||||
| /// <param name="reason"> | |||||
| /// The reason of the ban to be written in the audit log. | |||||
| /// The number of days to remove messages from this user for - must be between [0, 7]. | |||||
| /// </param> | /// </param> | ||||
| /// <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> | |||||
| Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); | Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Unbans the provided <paramref name="user"/> if they are currently banned. | |||||
| /// Unbans the provided user if they are currently banned. | |||||
| /// </summary> | /// </summary> | ||||
| Task RemoveBanAsync(IUser user, RequestOptions options = null); | Task RemoveBanAsync(IUser user, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| @@ -167,66 +178,114 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all channels in this guild. | /// Gets a collection of all channels in this guild. | ||||
| /// </summary> | /// </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> | |||||
| Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <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, or <see langword="null" /> if not found. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The channel ID.</param> | /// <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> | |||||
| Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all text channels in this guild. | /// Gets a collection of all text channels in this guild. | ||||
| /// </summary> | /// </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> | |||||
| Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a text channel in this guild with the provided ID, or <see langword="null"/> if not found. | |||||
| /// Gets a text channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The text channel ID.</param> | /// <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> | |||||
| Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all voice channels in this guild. | /// Gets a collection of all voice channels in this guild. | ||||
| /// </summary> | /// </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> | |||||
| Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all category channels in this guild. | /// Gets a collection of all category channels in this guild. | ||||
| /// </summary> | /// </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> | |||||
| Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <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, or <see langword="null" /> if not found. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The text channel ID.</param> | /// <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> | |||||
| Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the voice AFK channel in this guild with the provided ID, or <see langword="null"/> if not found. | |||||
| /// Gets the voice AFK channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||||
| /// </summary> | /// </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> | |||||
| Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the default system text channel in this guild with the provided ID, or <see langword="null"/> if | |||||
| /// Gets the default system text channel in this guild with the provided ID, or <see langword="null" /> if | |||||
| /// none is set. | /// none is set. | ||||
| /// </summary> | /// </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> | |||||
| Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the top viewable text channel in this guild with the provided ID, or <see langword="null"/> if not | |||||
| /// Gets the top viewable text channel in this guild with the provided ID, or <see langword="null" /> if not | |||||
| /// found. | /// found. | ||||
| /// </summary> | /// </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> | |||||
| Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the embed channel in this guild. | |||||
| /// 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. | |||||
| /// </summary> | /// </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> | |||||
| Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new text channel. | /// Creates a new text channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="name">The new name for the text channel.</param> | /// <param name="name">The new name for the text channel.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null); | Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new voice channel. | /// Creates a new voice channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="name">The new name for the voice channel.</param> | /// <param name="name">The new name for the voice channel.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null); | Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new channel category. | /// Creates a new channel category. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="name">The new name for the category.</param> | /// <param name="name">The new name for the category.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null); | Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null); | ||||
| Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null); | ||||
| @@ -249,24 +308,33 @@ namespace Discord | |||||
| /// <param name="permissions">The guild permission that the role should possess.</param> | /// <param name="permissions">The guild permission that the role should possess.</param> | ||||
| /// <param name="color">The color of the role.</param> | /// <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="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> | |||||
| Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); | Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all users in this guild. | /// Gets a collection of all users in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); //TODO: shouldnt this be paged? | |||||
| /// <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> | |||||
| Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the user in this guild with the provided ID, or <see langword="null"/> if not found. | /// Gets the user in this guild with the provided ID, or <see langword="null"/> if not found. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The user ID.</param> | /// <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> | |||||
| Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the current user for this guild. | /// Gets the current user for this guild. | ||||
| /// </summary> | /// </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> | |||||
| Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the owner of this guild. | /// Gets the owner of this guild. | ||||
| /// </summary> | /// </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> | |||||
| Task<IGuildUser> GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IGuildUser> GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Downloads all users for this guild if the current list is incomplete. | /// Downloads all users for this guild if the current list is incomplete. | ||||
| @@ -274,11 +342,12 @@ namespace Discord | |||||
| Task DownloadUsersAsync(); | Task DownloadUsersAsync(); | ||||
| /// <summary> | /// <summary> | ||||
| /// Removes all users from this guild if they have not logged on in a provided number of | /// Removes all users from this guild if they have not logged on in a provided number of | ||||
| /// <paramref name="days"/> or, if <paramref name="simulate"/> is true, returns the number of users that | |||||
| /// would be removed. | |||||
| /// <paramref name="days" /> or, if <paramref name="simulate" /> is <see langword="true"/>, returns the | |||||
| /// number of users that would be removed. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="days">The number of days required for the users to be kicked.</param> | /// <param name="days">The number of days required for the users to be kicked.</param> | ||||
| /// <param name="simulate">Whether this prune action is a simulation.</param> | /// <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> | /// <returns> | ||||
| /// The number of users removed from this guild. | /// The number of users removed from this guild. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -288,32 +357,41 @@ namespace Discord | |||||
| /// Gets the webhook in this guild with the provided ID, or <see langword="null"/> if not found. | /// Gets the webhook in this guild with the provided ID, or <see langword="null"/> if not found. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The webhook ID.</param> | /// <param name="id">The webhook ID.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); | Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all webhooks from this guild. | |||||
| /// Gets a collection of all webhook from this guild. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a specific emote from this guild. | /// Gets a specific emote from this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="id">The guild emote ID.</param> | /// <param name="id">The guild emote ID.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null); | Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new emote in this guild. | |||||
| /// Creates a new <see cref="GuildEmote"/> in this guild. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="name">The name of the guild emote.</param> | /// <param name="name">The name of the guild emote.</param> | ||||
| /// <param name="image">The image of the new emote.</param> | /// <param name="image">The image of the new emote.</param> | ||||
| /// <param name="roles">The roles to limit the emote usage to.</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> | |||||
| Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null); | Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Modifies an existing <paramref name="emote"/> in this guild. | |||||
| /// Modifies an existing <see cref="GuildEmote"/> in this guild. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="emote">The emote to be modified.</param> | |||||
| /// <param name="func">The properties to modify the emote with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null); | Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Deletes an existing <paramref name="emote"/> from this guild. | |||||
| /// Deletes an existing <see cref="GuildEmote"/> from this guild. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="emote">The guild emote to delete.</param> | |||||
| /// <param name="emote">The emote to delete.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); | Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,13 +3,14 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents whether the object is deletable or not. | |||||
| /// Determines whether the object is deletable or not. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IDeletable | public interface IDeletable | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Deletes this object and all its children. | /// Deletes this object and all its children. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task DeleteAsync(RequestOptions options = null); | Task DeleteAsync(RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,13 +1,16 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents whether the object is mentionable or not. | |||||
| /// Determines whether the object is mentionable or not. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IMentionable | public interface IMentionable | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns a special string used to mention this object. | /// Returns a special string used to mention this object. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// A string that is recognized by Discord as a mention (e.g. <@168693960628371456>). | |||||
| /// </returns> | |||||
| string Mention { get; } | string Mention { get; } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| using System; | |||||
| using System.IO; | using System.IO; | ||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -11,7 +12,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public Stream Stream { get; } | public Stream Stream { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Create the image with a <see cref="System.IO.Stream" /> . | |||||
| /// Create the image with a <see cref="System.IO.Stream" />. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="stream"> | /// <param name="stream"> | ||||
| /// The <see cref="System.IO.Stream" /> to create the image with. Note that this must be some type of stream | /// The <see cref="System.IO.Stream" /> to create the image with. Note that this must be some type of stream | ||||
| @@ -26,10 +27,30 @@ namespace Discord | |||||
| /// Create the image from a file path. | /// Create the image from a file path. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// This file <paramref name="path" /> is NOT validated, and is passed directly into a | |||||
| /// <see cref="File.OpenRead" /> | |||||
| /// This file path is NOT validated and is passed directly into a | |||||
| /// <see cref="File.OpenRead"/>. | |||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="path">The path to the file.</param> | /// <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" /> . | |||||
| /// </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 | |||||
| /// characters. | |||||
| /// </exception> | |||||
| /// <exception cref="NotSupportedException"><paramref name="path" /> is in an invalid format.</exception> | |||||
| /// <exception cref="DirectoryNotFoundException"> | |||||
| /// The specified <paramref name="path"/> is invalid, (for example, it is on an unmapped drive). | |||||
| /// </exception> | |||||
| /// <exception cref="UnauthorizedAccessException"> | |||||
| /// <paramref name="path" /> specified a directory.-or- The caller does not have the required permission. | |||||
| /// </exception> | |||||
| /// <exception cref="FileNotFoundException">The file specified in <paramref name="path" /> was not found. | |||||
| /// </exception> | |||||
| /// <exception cref="IOException">An I/O error occurred while opening the file. </exception> | |||||
| public Image(string path) | public Image(string path) | ||||
| { | { | ||||
| Stream = File.OpenRead(path); | Stream = File.OpenRead(path); | ||||
| @@ -41,6 +41,9 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> Gets or sets the title of an <see cref="Embed"/>. </summary> | /// <summary> Gets or sets the title of an <see cref="Embed"/>. </summary> | ||||
| /// <exception cref="ArgumentException" accessor="set">Title length exceeds the maximum allowed by Discord. | |||||
| /// </exception> | |||||
| /// <returns> The title of the embed.</returns> | |||||
| public string Title | public string Title | ||||
| { | { | ||||
| get => _title; | get => _title; | ||||
| @@ -50,7 +53,10 @@ namespace Discord | |||||
| _title = value; | _title = value; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> Gets or sets the description of an <see cref="Embed"/>. </summary> | /// <summary> Gets or sets the description of an <see cref="Embed"/>. </summary> | ||||
| /// <exception cref="ArgumentException" accessor="set">Description length exceeds the maximum allowed by Discord.</exception> | |||||
| /// <returns> The description of the embed.</returns> | |||||
| public string Description | public string Description | ||||
| { | { | ||||
| get => _description; | get => _description; | ||||
| @@ -62,6 +68,8 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary> | /// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary> | ||||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||||
| /// <returns> The URL of the embed.</returns> | |||||
| public string Url | public string Url | ||||
| { | { | ||||
| get => _url; | get => _url; | ||||
| @@ -72,6 +80,8 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> Gets or sets the thumbnail URL of an <see cref="Embed"/>. </summary> | /// <summary> Gets or sets the thumbnail URL of an <see cref="Embed"/>. </summary> | ||||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||||
| /// <returns> The thumbnail URL of the embed.</returns> | |||||
| public string ThumbnailUrl | public string ThumbnailUrl | ||||
| { | { | ||||
| get => _thumbnail?.Url; | get => _thumbnail?.Url; | ||||
| @@ -82,6 +92,8 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> Gets or sets the image URL of an <see cref="Embed"/>. </summary> | /// <summary> Gets or sets the image URL of an <see cref="Embed"/>. </summary> | ||||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||||
| /// <returns> The image URL of the embed.</returns> | |||||
| public string ImageUrl | public string ImageUrl | ||||
| { | { | ||||
| get => _image?.Url; | get => _image?.Url; | ||||
| @@ -91,7 +103,13 @@ namespace Discord | |||||
| _image = new EmbedImage(value, null, null, null); | _image = new EmbedImage(value, null, null, null); | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> Gets or sets the list of <see cref="EmbedFieldBuilder"/> of an <see cref="Embed"/>. </summary> | /// <summary> Gets or sets the list of <see cref="EmbedFieldBuilder"/> of an <see cref="Embed"/>. </summary> | ||||
| /// <exception cref="ArgumentNullException" accessor="set">An embed builder's fields collection is set to | |||||
| /// <see langword="null"/>.</exception> | |||||
| /// <exception cref="ArgumentException" accessor="set">Description length exceeds the maximum allowed by | |||||
| /// Discord.</exception> | |||||
| /// <returns> The list of existing <see cref="EmbedFieldBuilder"/>.</returns> | |||||
| public List<EmbedFieldBuilder> Fields | public List<EmbedFieldBuilder> Fields | ||||
| { | { | ||||
| get => _fields; | get => _fields; | ||||
| @@ -103,18 +121,42 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> Gets or sets the timestamp of an <see cref="Embed"/>. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the timestamp of an <see cref="Embed" />. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The timestamp of the embed, or <see langword="null" /> if none is set. | |||||
| /// </returns> | |||||
| public DateTimeOffset? Timestamp { get; set; } | public DateTimeOffset? Timestamp { get; set; } | ||||
| /// <summary> Gets or sets the sidebar color of an <see cref="Embed"/>. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the sidebar color of an <see cref="Embed" />. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The color of the embed, or <see langword="null" /> if none is set. | |||||
| /// </returns> | |||||
| public Color? Color { get; set; } | public Color? Color { get; set; } | ||||
| /// <summary> Gets or sets the <see cref="EmbedAuthorBuilder"/> of an <see cref="Embed"/>. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the <see cref="EmbedAuthorBuilder" /> of an <see cref="Embed" />. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The author field builder of the embed, or <see langword="null" /> if none is set. | |||||
| /// </returns> | |||||
| public EmbedAuthorBuilder Author { get; set; } | public EmbedAuthorBuilder Author { get; set; } | ||||
| /// <summary> Gets or sets the <see cref="EmbedFooterBuilder"/> of an <see cref="Embed"/>. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the <see cref="EmbedFooterBuilder" /> of an <see cref="Embed" />. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The footer field builder of the embed, or <see langword="null" /> if none is set. | |||||
| /// </returns> | |||||
| public EmbedFooterBuilder Footer { get; set; } | public EmbedFooterBuilder Footer { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the total length of all embed properties. | /// Gets the total length of all embed properties. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The combined length of <see cref="Title"/>, <see cref="EmbedAuthor.Name"/>, <see cref="Description"/>, | |||||
| /// <see cref="EmbedFooter.Text"/>, <see cref="EmbedField.Name"/>, and <see cref="EmbedField.Value"/>. | |||||
| /// </returns> | |||||
| public int Length | public int Length | ||||
| { | { | ||||
| get | get | ||||
| @@ -130,9 +172,12 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the title of an <see cref="Embed" />. | |||||
| /// Sets the title of an <see cref="Embed" /> . | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="title">The title to be set.</param> | /// <param name="title">The title to be set.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithTitle(string title) | public EmbedBuilder WithTitle(string title) | ||||
| { | { | ||||
| Title = title; | Title = title; | ||||
| @@ -142,6 +187,9 @@ namespace Discord | |||||
| /// Sets the description of an <see cref="Embed"/>. | /// Sets the description of an <see cref="Embed"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="description"> The description to be set. </param> | /// <param name="description"> The description to be set. </param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithDescription(string description) | public EmbedBuilder WithDescription(string description) | ||||
| { | { | ||||
| Description = description; | Description = description; | ||||
| @@ -151,6 +199,9 @@ namespace Discord | |||||
| /// Sets the URL of an <see cref="Embed"/>. | /// Sets the URL of an <see cref="Embed"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="url"> The URL to be set. </param> | /// <param name="url"> The URL to be set. </param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithUrl(string url) | public EmbedBuilder WithUrl(string url) | ||||
| { | { | ||||
| Url = url; | Url = url; | ||||
| @@ -160,15 +211,21 @@ namespace Discord | |||||
| /// Sets the thumbnail URL of an <see cref="Embed"/>. | /// Sets the thumbnail URL of an <see cref="Embed"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="thumbnailUrl"> The thumbnail URL to be set. </param> | /// <param name="thumbnailUrl"> The thumbnail URL to be set. </param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) | public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) | ||||
| { | { | ||||
| ThumbnailUrl = thumbnailUrl; | ThumbnailUrl = thumbnailUrl; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the image URL of an <see cref="Embed" /> . | |||||
| /// Sets the image URL of an <see cref="Embed" />. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="imageUrl">The image URL to be set.</param> | /// <param name="imageUrl">The image URL to be set.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithImageUrl(string imageUrl) | public EmbedBuilder WithImageUrl(string imageUrl) | ||||
| { | { | ||||
| ImageUrl = imageUrl; | ImageUrl = imageUrl; | ||||
| @@ -177,24 +234,33 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets the timestamp of an <see cref="Embed" /> to the current time. | /// Sets the timestamp of an <see cref="Embed" /> to the current time. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithCurrentTimestamp() | public EmbedBuilder WithCurrentTimestamp() | ||||
| { | { | ||||
| Timestamp = DateTimeOffset.UtcNow; | Timestamp = DateTimeOffset.UtcNow; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the timestamp of an <see cref="Embed" /> . | |||||
| /// Sets the timestamp of an <see cref="Embed" />. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="dateTimeOffset">The timestamp to be set.</param> | /// <param name="dateTimeOffset">The timestamp to be set.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) | public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) | ||||
| { | { | ||||
| Timestamp = dateTimeOffset; | Timestamp = dateTimeOffset; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the sidebar color of an <see cref="Embed" /> . | |||||
| /// Sets the sidebar color of an <see cref="Embed" />. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="color">The color to be set.</param> | /// <param name="color">The color to be set.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithColor(Color color) | public EmbedBuilder WithColor(Color color) | ||||
| { | { | ||||
| Color = color; | Color = color; | ||||
| @@ -202,9 +268,12 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the <see cref="EmbedAuthorBuilder" /> of an <see cref="Embed" /> . | |||||
| /// Sets the <see cref="EmbedAuthorBuilder" /> of an <see cref="Embed" />. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="author">The author builder class containing the author field properties.</param> | /// <param name="author">The author builder class containing the author field properties.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) | public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) | ||||
| { | { | ||||
| Author = author; | Author = author; | ||||
| @@ -214,6 +283,9 @@ namespace Discord | |||||
| /// Sets the author field of an <see cref="Embed" /> with the provided properties. | /// Sets the author field of an <see cref="Embed" /> with the provided properties. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="action">The <see langword="delegate"/> containing the author field properties.</param> | /// <param name="action">The <see langword="delegate"/> containing the author field properties.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithAuthor(Action<EmbedAuthorBuilder> action) | public EmbedBuilder WithAuthor(Action<EmbedAuthorBuilder> action) | ||||
| { | { | ||||
| var author = new EmbedAuthorBuilder(); | var author = new EmbedAuthorBuilder(); | ||||
| @@ -227,6 +299,9 @@ namespace Discord | |||||
| /// <param name="name">The title of the author field.</param> | /// <param name="name">The title of the author field.</param> | ||||
| /// <param name="iconUrl">The icon URL of the author field.</param> | /// <param name="iconUrl">The icon URL of the author field.</param> | ||||
| /// <param name="url">The URL of the author field.</param> | /// <param name="url">The URL of the author field.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) | public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) | ||||
| { | { | ||||
| var author = new EmbedAuthorBuilder | var author = new EmbedAuthorBuilder | ||||
| @@ -239,9 +314,12 @@ namespace Discord | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the <see cref="EmbedFooterBuilder" /> of an <see cref="Embed" /> . | |||||
| /// Sets the <see cref="EmbedFooterBuilder" /> of an <see cref="Embed" />. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="footer">The footer builder class containing the footer field properties.</param> | /// <param name="footer">The footer builder class containing the footer field properties.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithFooter(EmbedFooterBuilder footer) | public EmbedBuilder WithFooter(EmbedFooterBuilder footer) | ||||
| { | { | ||||
| Footer = footer; | Footer = footer; | ||||
| @@ -251,6 +329,9 @@ namespace Discord | |||||
| /// Sets the footer field of an <see cref="Embed" /> with the provided properties. | /// Sets the footer field of an <see cref="Embed" /> with the provided properties. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="action">The <see langword="delegate"/> containing the footer field properties.</param> | /// <param name="action">The <see langword="delegate"/> containing the footer field properties.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithFooter(Action<EmbedFooterBuilder> action) | public EmbedBuilder WithFooter(Action<EmbedFooterBuilder> action) | ||||
| { | { | ||||
| var footer = new EmbedFooterBuilder(); | var footer = new EmbedFooterBuilder(); | ||||
| @@ -263,6 +344,9 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="text">The title of the footer field.</param> | /// <param name="text">The title of the footer field.</param> | ||||
| /// <param name="iconUrl">The icon URL of the footer field.</param> | /// <param name="iconUrl">The icon URL of the footer field.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder WithFooter(string text, string iconUrl = null) | public EmbedBuilder WithFooter(string text, string iconUrl = null) | ||||
| { | { | ||||
| var footer = new EmbedFooterBuilder | var footer = new EmbedFooterBuilder | ||||
| @@ -280,6 +364,9 @@ namespace Discord | |||||
| /// <param name="name">The title of the field.</param> | /// <param name="name">The title of the field.</param> | ||||
| /// <param name="value">The value of the field.</param> | /// <param name="value">The value of the field.</param> | ||||
| /// <param name="inline">Indicates whether the field is in-line or not.</param> | /// <param name="inline">Indicates whether the field is in-line or not.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder AddField(string name, object value, bool inline = false) | public EmbedBuilder AddField(string name, object value, bool inline = false) | ||||
| { | { | ||||
| var field = new EmbedFieldBuilder() | var field = new EmbedFieldBuilder() | ||||
| @@ -289,11 +376,16 @@ namespace Discord | |||||
| AddField(field); | AddField(field); | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds a field with the provided <see cref="EmbedFieldBuilder" /> to an | /// Adds a field with the provided <see cref="EmbedFieldBuilder" /> to an | ||||
| /// <see cref="Embed" />. | /// <see cref="Embed" />. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="field">The field builder class containing the field properties.</param> | /// <param name="field">The field builder class containing the field properties.</param> | ||||
| /// <exception cref="ArgumentException">Field count exceeds the maximum allowed by Discord.</exception> | |||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder AddField(EmbedFieldBuilder field) | public EmbedBuilder AddField(EmbedFieldBuilder field) | ||||
| { | { | ||||
| if (Fields.Count >= MaxFieldCount) | if (Fields.Count >= MaxFieldCount) | ||||
| @@ -308,6 +400,9 @@ namespace Discord | |||||
| /// Adds an <see cref="Embed" /> field with the provided properties. | /// Adds an <see cref="Embed" /> field with the provided properties. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="action">The <see langword="delegate"/> containing the field properties.</param> | /// <param name="action">The <see langword="delegate"/> containing the field properties.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedBuilder AddField(Action<EmbedFieldBuilder> action) | public EmbedBuilder AddField(Action<EmbedFieldBuilder> action) | ||||
| { | { | ||||
| var field = new EmbedFieldBuilder(); | var field = new EmbedFieldBuilder(); | ||||
| @@ -317,11 +412,12 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Builds the <see cref="Embed" /> into a Rich Embed format. | |||||
| /// Builds the <see cref="Embed" /> into a Rich Embed ready to be sent. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// The built embed object. | /// The built embed object. | ||||
| /// </returns> | /// </returns> | ||||
| /// <exception cref="InvalidOperationException">Total embed length exceeds the maximum allowed by Discord.</exception> | |||||
| public Embed Build() | public Embed Build() | ||||
| { | { | ||||
| if (Length > MaxEmbedLength) | if (Length > MaxEmbedLength) | ||||
| @@ -335,6 +431,9 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Represents a builder class for an embed field. | |||||
| /// </summary> | |||||
| public class EmbedFieldBuilder | public class EmbedFieldBuilder | ||||
| { | { | ||||
| private string _name; | private string _name; | ||||
| @@ -352,6 +451,14 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the field name. | /// Gets or sets the field name. | ||||
| /// </summary> | /// </summary> | ||||
| /// <exception cref="ArgumentException"> | |||||
| /// <para>Field name is <see langword="null" />, empty or entirely whitespace.</para> | |||||
| /// <para><c>- or -</c></para> | |||||
| /// <para>Field name length exceeds <see cref="MaxFieldNameLength"/>.</para> | |||||
| /// </exception> | |||||
| /// <returns> | |||||
| /// The name of the field. | |||||
| /// </returns> | |||||
| public string Name | public string Name | ||||
| { | { | ||||
| get => _name; | get => _name; | ||||
| @@ -366,19 +473,27 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the field value. | /// Gets or sets the field value. | ||||
| /// </summary> | /// </summary> | ||||
| /// <exception cref="ArgumentException" accessor="set"> | |||||
| /// <para>Field value is <see langword="null" />, empty or entirely whitespace.</para> | |||||
| /// <para><c>- or -</c></para> | |||||
| /// <para>Field value length exceeds <see cref="MaxFieldValueLength"/>.</para> | |||||
| /// </exception> | |||||
| /// <returns> | |||||
| /// The value of the field. | |||||
| /// </returns> | |||||
| public object Value | public object Value | ||||
| { | { | ||||
| get => _value; | get => _value; | ||||
| set | set | ||||
| { | { | ||||
| var stringValue = value?.ToString(); | var stringValue = value?.ToString(); | ||||
| if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); | |||||
| if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); | |||||
| if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); | if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); | ||||
| _value = stringValue; | _value = stringValue; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets whether the field should be in-line with each other. | |||||
| /// Determines whether the field should be in-line with each other. | |||||
| /// </summary> | /// </summary> | ||||
| public bool IsInline { get; set; } | public bool IsInline { get; set; } | ||||
| @@ -386,6 +501,9 @@ namespace Discord | |||||
| /// Sets the field name. | /// Sets the field name. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="name">The name to set the field name to.</param> | /// <param name="name">The name to set the field name to.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedFieldBuilder WithName(string name) | public EmbedFieldBuilder WithName(string name) | ||||
| { | { | ||||
| Name = name; | Name = name; | ||||
| @@ -395,14 +513,20 @@ namespace Discord | |||||
| /// Sets the field value. | /// Sets the field value. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="value">The value to set the field value to.</param> | /// <param name="value">The value to set the field value to.</param> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedFieldBuilder WithValue(object value) | public EmbedFieldBuilder WithValue(object value) | ||||
| { | { | ||||
| Value = value; | Value = value; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets whether the field should be in-line with each other. | |||||
| /// Determines whether the field should be in-line with each other. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedFieldBuilder WithIsInline(bool isInline) | public EmbedFieldBuilder WithIsInline(bool isInline) | ||||
| { | { | ||||
| IsInline = isInline; | IsInline = isInline; | ||||
| @@ -410,19 +534,42 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Builds the field builder into a <see cref="EmbedField"/> class. | |||||
| /// Builds the field builder into a <see cref="EmbedField" /> class. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| /// <exception cref="ArgumentException"> | |||||
| /// <para><see cref="Name"/> or <see cref="Value"/> is <see langword="null" />, empty or entirely whitespace.</para> | |||||
| /// <para><c>- or -</c></para> | |||||
| /// <para><see cref="Name"/> or <see cref="Value"/> length exceeds the maximum allowed by Discord.</para> | |||||
| /// </exception> | |||||
| public EmbedField Build() | public EmbedField Build() | ||||
| => new EmbedField(Name, Value.ToString(), IsInline); | => new EmbedField(Name, Value.ToString(), IsInline); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Represents a builder class for a author field. | |||||
| /// </summary> | |||||
| public class EmbedAuthorBuilder | public class EmbedAuthorBuilder | ||||
| { | { | ||||
| private string _name; | private string _name; | ||||
| private string _url; | private string _url; | ||||
| private string _iconUrl; | private string _iconUrl; | ||||
| /// <summary> | |||||
| /// Gets the maximum author name length allowed by Discord. | |||||
| /// </summary> | |||||
| public const int MaxAuthorNameLength = 256; | public const int MaxAuthorNameLength = 256; | ||||
| /// <summary> | |||||
| /// Gets or sets the author name. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException"> | |||||
| /// Author name length is longer than <see cref="MaxAuthorNameLength" />. | |||||
| /// </exception> | |||||
| /// <returns> | |||||
| /// The author name. | |||||
| /// </returns> | |||||
| public string Name | public string Name | ||||
| { | { | ||||
| get => _name; | get => _name; | ||||
| @@ -432,6 +579,13 @@ namespace Discord | |||||
| _name = value; | _name = value; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets or sets the URL of the author field. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||||
| /// <returns> | |||||
| /// The URL of the author field. | |||||
| /// </returns> | |||||
| public string Url | public string Url | ||||
| { | { | ||||
| get => _url; | get => _url; | ||||
| @@ -441,6 +595,13 @@ namespace Discord | |||||
| _url = value; | _url = value; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets or sets the icon URL of the author field. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||||
| /// <returns> | |||||
| /// The icon URL of the author field. | |||||
| /// </returns> | |||||
| public string IconUrl | public string IconUrl | ||||
| { | { | ||||
| get => _iconUrl; | get => _iconUrl; | ||||
| @@ -451,33 +612,82 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the name of the author field. | |||||
| /// </summary> | |||||
| /// <param name="name">The name of the author field.</param> | |||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedAuthorBuilder WithName(string name) | public EmbedAuthorBuilder WithName(string name) | ||||
| { | { | ||||
| Name = name; | Name = name; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the URL of the author field. | |||||
| /// </summary> | |||||
| /// <param name="url">The URL of the author field.</param> | |||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedAuthorBuilder WithUrl(string url) | public EmbedAuthorBuilder WithUrl(string url) | ||||
| { | { | ||||
| Url = url; | Url = url; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the icon URL of the author field. | |||||
| /// </summary> | |||||
| /// <param name="iconUrl">The icon URL of the author field.</param> | |||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedAuthorBuilder WithIconUrl(string iconUrl) | public EmbedAuthorBuilder WithIconUrl(string iconUrl) | ||||
| { | { | ||||
| IconUrl = iconUrl; | IconUrl = iconUrl; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Builds the author field to be used. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException"> | |||||
| /// <para>Author name length is longer than <see cref="MaxAuthorNameLength" />.</para> | |||||
| /// <para><c>- or -</c></para> | |||||
| /// <para><see cref="Url"/> is not a well-formed <see cref="Uri" />.</para> | |||||
| /// <para><c>- or -</c></para> | |||||
| /// <para><see cref="IconUrl"/> is not a well-formed <see cref="Uri" />.</para> | |||||
| /// </exception> | |||||
| /// <returns> | |||||
| /// The built author field. | |||||
| /// </returns> | |||||
| public EmbedAuthor Build() | public EmbedAuthor Build() | ||||
| => new EmbedAuthor(Name, Url, IconUrl, null); | => new EmbedAuthor(Name, Url, IconUrl, null); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Represents a builder class for an embed footer. | |||||
| /// </summary> | |||||
| public class EmbedFooterBuilder | public class EmbedFooterBuilder | ||||
| { | { | ||||
| private string _text; | private string _text; | ||||
| private string _iconUrl; | private string _iconUrl; | ||||
| /// <summary> | |||||
| /// Gets the maximum footer length allowed by Discord. | |||||
| /// </summary> | |||||
| public const int MaxFooterTextLength = 2048; | public const int MaxFooterTextLength = 2048; | ||||
| /// <summary> | |||||
| /// Gets or sets the footer text. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException"> | |||||
| /// Author name length is longer than <see cref="MaxFooterTextLength" />. | |||||
| /// </exception> | |||||
| /// <returns> | |||||
| /// The footer text. | |||||
| /// </returns> | |||||
| public string Text | public string Text | ||||
| { | { | ||||
| get => _text; | get => _text; | ||||
| @@ -487,6 +697,13 @@ namespace Discord | |||||
| _text = value; | _text = value; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets or sets the icon URL of the footer field. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||||
| /// <returns> | |||||
| /// The icon URL of the footer field. | |||||
| /// </returns> | |||||
| public string IconUrl | public string IconUrl | ||||
| { | { | ||||
| get => _iconUrl; | get => _iconUrl; | ||||
| @@ -497,17 +714,43 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the name of the footer field. | |||||
| /// </summary> | |||||
| /// <param name="text">The text of the footer field.</param> | |||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedFooterBuilder WithText(string text) | public EmbedFooterBuilder WithText(string text) | ||||
| { | { | ||||
| Text = text; | Text = text; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the icon URL of the footer field. | |||||
| /// </summary> | |||||
| /// <param name="iconUrl">The icon URL of the footer field.</param> | |||||
| /// <returns> | |||||
| /// The current builder. | |||||
| /// </returns> | |||||
| public EmbedFooterBuilder WithIconUrl(string iconUrl) | public EmbedFooterBuilder WithIconUrl(string iconUrl) | ||||
| { | { | ||||
| IconUrl = iconUrl; | IconUrl = iconUrl; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Builds the footer field to be used. | |||||
| /// </summary> | |||||
| /// <returns></returns> | |||||
| /// <exception cref="ArgumentException"> | |||||
| /// <para><see cref="Text"/> length is longer than <see cref="MaxFooterTextLength" />.</para> | |||||
| /// <para><c>- or -</c></para> | |||||
| /// <para><see cref="IconUrl"/> is not a well-formed <see cref="Uri"/>.</para> | |||||
| /// </exception> | |||||
| /// <returns> | |||||
| /// A built footer field. | |||||
| /// </returns> | |||||
| public EmbedFooter Build() | public EmbedFooter Build() | ||||
| => new EmbedFooter(Text, IconUrl, null); | => new EmbedFooter(Text, IconUrl, null); | ||||
| } | } | ||||
| @@ -17,7 +17,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public string Value { get; internal set; } | public string Value { get; internal set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets whether the field should be in-line with each other. | |||||
| /// Determines whether the field should be in-line with each other. | |||||
| /// </summary> | /// </summary> | ||||
| public bool Inline { get; internal set; } | public bool Inline { get; internal set; } | ||||
| @@ -83,12 +83,14 @@ namespace Discord | |||||
| ((uint)g << 8) | | ((uint)g << 8) | | ||||
| (uint)b; | (uint)b; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Initializes a <see cref="Color"/> struct with the given RGB value. | /// Initializes a <see cref="Color"/> struct with the given RGB value. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="r">The value that represents the red color. Must be within 0~255.</param> | /// <param name="r">The value that represents the red color. Must be within 0~255.</param> | ||||
| /// <param name="g">The value that represents the green color. Must be within 0~255.</param> | /// <param name="g">The value that represents the green color. Must be within 0~255.</param> | ||||
| /// <param name="b">The value that represents the blue color. Must be within 0~255.</param> | /// <param name="b">The value that represents the blue color. Must be within 0~255.</param> | ||||
| /// <exception cref="ArgumentOutOfRangeException">The argument value is not between 0 to 255.</exception> | |||||
| public Color(int r, int g, int b) | public Color(int r, int g, int b) | ||||
| { | { | ||||
| if (r < 0 || r > 255) | if (r < 0 || r > 255) | ||||
| @@ -108,6 +110,7 @@ namespace Discord | |||||
| /// <param name="r">The value that represents the red color. Must be within 0~1.</param> | /// <param name="r">The value that represents the red color. Must be within 0~1.</param> | ||||
| /// <param name="g">The value that represents the green color. Must be within 0~1.</param> | /// <param name="g">The value that represents the green color. Must be within 0~1.</param> | ||||
| /// <param name="b">The value that represents the blue color. Must be within 0~1.</param> | /// <param name="b">The value that represents the blue color. Must be within 0~1.</param> | ||||
| /// <exception cref="ArgumentOutOfRangeException">The argument value is not between 0 to 1.</exception> | |||||
| public Color(float r, float g, float b) | public Color(float r, float g, float b) | ||||
| { | { | ||||
| if (r < 0.0f || r > 1.0f) | if (r < 0.0f || r > 1.0f) | ||||
| @@ -18,16 +18,28 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| Color Color { get; } | Color Color { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns <see langword="true"/> if users of this role are separated in the user list. | |||||
| /// Determines whether the role can be separated in the user list. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// Returns <see langword="true"/> if users of this role are separated in the user list; otherwise, returns | |||||
| /// <see langword="false"/>. | |||||
| /// </returns> | |||||
| bool IsHoisted { get; } | bool IsHoisted { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns <see langword="true"/> if this role is automatically managed by Discord. | |||||
| /// Determines whether the role is managed by Discord. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// Returns <see langword="true"/> if this role is automatically managed by Discord; otherwise, returns | |||||
| /// <see langword="false"/>. | |||||
| /// </returns> | |||||
| bool IsManaged { get; } | bool IsManaged { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns <see langword="true"/> if this role may be mentioned in messages. | |||||
| /// Determines whether the role is mentionable. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | |||||
| /// Returns <see langword="true"/> if this role may be mentioned in messages; otherwise, returns | |||||
| /// <see langword="false"/>. | |||||
| /// </returns> | |||||
| bool IsMentionable { get; } | bool IsMentionable { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the name of this role. | /// Gets the name of this role. | ||||
| @@ -45,6 +57,8 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this role. | /// Modifies this role. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the role with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -5,7 +5,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a Discord user that is in a guild. | |||||
| /// Represents a generic guild user. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IGuildUser : IUser, IVoiceState | public interface IGuildUser : IUser, IVoiceState | ||||
| { | { | ||||
| @@ -46,31 +46,38 @@ namespace Discord | |||||
| /// Kicks this user from this guild. | /// Kicks this user from this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="reason">The reason for the kick which will be recorded in the audit log.</param> | /// <param name="reason">The reason for the kick which will be recorded in the audit log.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task KickAsync(string reason = null, RequestOptions options = null); | Task KickAsync(string reason = null, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this user's properties in this guild. | /// Modifies this user's properties in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="func">The properties to modify the user with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds a <paramref name="role"/> to this user in this guild. | /// Adds a <paramref name="role"/> to this user in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="role">The role to be added to the user.</param> | /// <param name="role">The role to be added to the user.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task AddRoleAsync(IRole role, RequestOptions options = null); | Task AddRoleAsync(IRole role, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds <paramref name="roles"/> to this user in this guild. | /// Adds <paramref name="roles"/> to this user in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="roles">The roles to be added to the user.</param> | /// <param name="roles">The roles to be added to the user.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null); | Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Removes a <paramref name="role"/> from this user in this guild. | /// Removes a <paramref name="role"/> from this user in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="role">The role to be removed from the user.</param> | /// <param name="role">The role to be removed from the user.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task RemoveRoleAsync(IRole role, RequestOptions options = null); | Task RemoveRoleAsync(IRole role, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Removes <paramref name="roles"/> from this user in this guild. | /// Removes <paramref name="roles"/> from this user in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="roles">The roles to be removed from the user.</param> | /// <param name="roles">The roles to be removed from the user.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null); | Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,7 +3,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a Discord user. | |||||
| /// Represents a generic user. | |||||
| /// </summary> | /// </summary> | ||||
| public interface IUser : ISnowflakeEntity, IMentionable, IPresence | public interface IUser : ISnowflakeEntity, IMentionable, IPresence | ||||
| { | { | ||||
| @@ -41,8 +41,7 @@ namespace Discord | |||||
| string Username { get; } | string Username { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns a private message channel to this user, creating one if it does not already | |||||
| /// exist. | |||||
| /// Returns a direct message channel to this user, or create one if it does not already exist. | |||||
| /// </summary> | /// </summary> | ||||
| Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | ||||
| } | } | ||||
| @@ -26,7 +26,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| bool IsSuppressed { get; } | bool IsSuppressed { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the voice channel this user is currently in, if any. | |||||
| /// Gets the voice channel this user is currently in, or <see langword="null"/> if none. | |||||
| /// </summary> | /// </summary> | ||||
| IVoiceChannel VoiceChannel { get; } | IVoiceChannel VoiceChannel { get; } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -30,6 +30,7 @@ namespace Discord | |||||
| builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | ||||
| /// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary> | /// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary> | ||||
| /// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception> | |||||
| public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) | public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) | ||||
| { | { | ||||
| if (embed.Type != EmbedType.Rich) | if (embed.Type != EmbedType.Rich) | ||||
| @@ -5,6 +5,9 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a generic Discord client. | |||||
| /// </summary> | |||||
| public interface IDiscordClient : IDisposable | public interface IDiscordClient : IDisposable | ||||
| { | { | ||||
| ConnectionState ConnectionState { get; } | ConnectionState ConnectionState { get; } | ||||
| @@ -14,11 +17,37 @@ namespace Discord | |||||
| Task StartAsync(); | Task StartAsync(); | ||||
| Task StopAsync(); | Task StopAsync(); | ||||
| /// <summary> | |||||
| /// Gets the application information associated with this account. | |||||
| /// </summary> | |||||
| Task<IApplication> GetApplicationInfoAsync(RequestOptions options = null); | Task<IApplication> GetApplicationInfoAsync(RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Gets a generic channel with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The ID of the channel.</param> | |||||
| /// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||||
| Task<IChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Gets a list of private channels. | |||||
| /// </summary> | |||||
| /// <param name="mode"> | |||||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||||
| /// </param> | |||||
| Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Gets a list of direct message channels. | |||||
| /// </summary> | |||||
| /// <param name="mode"> | |||||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||||
| /// </param> | |||||
| Task<IReadOnlyCollection<IDMChannel>> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<IDMChannel>> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Gets a list of group channels. | |||||
| /// </summary> | |||||
| /// <param name="mode"> | |||||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||||
| /// </param> | |||||
| Task<IReadOnlyCollection<IGroupChannel>> GetGroupChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<IGroupChannel>> GetGroupChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| Task<IReadOnlyCollection<IConnection>> GetConnectionsAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IConnection>> GetConnectionsAsync(RequestOptions options = null); | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Logging | namespace Discord.Logging | ||||
| @@ -24,7 +24,10 @@ namespace Discord.Logging | |||||
| if (severity <= Level) | if (severity <= Level) | ||||
| await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); | await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); | ||||
| } | } | ||||
| catch { } | |||||
| catch | |||||
| { | |||||
| // ignored | |||||
| } | |||||
| } | } | ||||
| public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) | public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) | ||||
| { | { | ||||
| @@ -33,7 +36,10 @@ namespace Discord.Logging | |||||
| if (severity <= Level) | if (severity <= Level) | ||||
| await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); | await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); | ||||
| } | } | ||||
| catch { } | |||||
| catch | |||||
| { | |||||
| // ignored | |||||
| } | |||||
| } | } | ||||
| #if FORMATSTR | #if FORMATSTR | ||||
| public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) | public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) | ||||
| @@ -44,7 +44,7 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Initializes a new <see cref="RequestOptions" /> class with the default request timeout set in | /// Initializes a new <see cref="RequestOptions" /> class with the default request timeout set in | ||||
| /// <see cref="DiscordConfig" /> . | |||||
| /// <see cref="DiscordConfig" />. | |||||
| /// </summary> | /// </summary> | ||||
| public RequestOptions() | public RequestOptions() | ||||
| { | { | ||||
| @@ -31,11 +31,12 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Parses a provided user mention string. | /// Parses a provided user mention string. | ||||
| /// </summary> | /// </summary> | ||||
| /// <exception cref="ArgumentException">Invalid mention format.</exception> | |||||
| public static ulong ParseUser(string text) | public static ulong ParseUser(string text) | ||||
| { | { | ||||
| if (TryParseUser(text, out ulong id)) | if (TryParseUser(text, out ulong id)) | ||||
| return id; | return id; | ||||
| throw new ArgumentException("Invalid mention format", nameof(text)); | |||||
| throw new ArgumentException("Invalid mention format.", nameof(text)); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Tries to parse a provided user mention string. | /// Tries to parse a provided user mention string. | ||||
| @@ -59,11 +60,12 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Parses a provided channel mention string. | /// Parses a provided channel mention string. | ||||
| /// </summary> | /// </summary> | ||||
| /// <exception cref="ArgumentException">Invalid mention format.</exception> | |||||
| public static ulong ParseChannel(string text) | public static ulong ParseChannel(string text) | ||||
| { | { | ||||
| if (TryParseChannel(text, out ulong id)) | if (TryParseChannel(text, out ulong id)) | ||||
| return id; | return id; | ||||
| throw new ArgumentException("Invalid mention format", nameof(text)); | |||||
| throw new ArgumentException("Invalid mention format.", nameof(text)); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Tries to parse a provided channel mention string. | /// Tries to parse a provided channel mention string. | ||||
| @@ -84,11 +86,12 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Parses a provided role mention string. | /// Parses a provided role mention string. | ||||
| /// </summary> | /// </summary> | ||||
| /// <exception cref="ArgumentException">Invalid mention format.</exception> | |||||
| public static ulong ParseRole(string text) | public static ulong ParseRole(string text) | ||||
| { | { | ||||
| if (TryParseRole(text, out ulong id)) | if (TryParseRole(text, out ulong id)) | ||||
| return id; | return id; | ||||
| throw new ArgumentException("Invalid mention format", nameof(text)); | |||||
| throw new ArgumentException("Invalid mention format.", nameof(text)); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Tries to parse a provided role mention string. | /// Tries to parse a provided role mention string. | ||||
| @@ -163,22 +166,22 @@ namespace Discord | |||||
| if (user != null) | if (user != null) | ||||
| return $"@{guildUser?.Nickname ?? user?.Username}"; | return $"@{guildUser?.Nickname ?? user?.Username}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.NameNoPrefix: | case TagHandling.NameNoPrefix: | ||||
| if (user != null) | if (user != null) | ||||
| return $"{guildUser?.Nickname ?? user?.Username}"; | return $"{guildUser?.Nickname ?? user?.Username}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.FullName: | case TagHandling.FullName: | ||||
| if (user != null) | if (user != null) | ||||
| return $"@{user.Username}#{user.Discriminator}"; | return $"@{user.Username}#{user.Discriminator}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.FullNameNoPrefix: | case TagHandling.FullNameNoPrefix: | ||||
| if (user != null) | if (user != null) | ||||
| return $"{user.Username}#{user.Discriminator}"; | return $"{user.Username}#{user.Discriminator}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
| if (guildUser != null && guildUser.Nickname == null) | if (guildUser != null && guildUser.Nickname == null) | ||||
| return MentionUser($"{SanitizeChar}{tag.Key}", false); | return MentionUser($"{SanitizeChar}{tag.Key}", false); | ||||
| @@ -200,13 +203,13 @@ namespace Discord | |||||
| if (channel != null) | if (channel != null) | ||||
| return $"#{channel.Name}"; | return $"#{channel.Name}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.NameNoPrefix: | case TagHandling.NameNoPrefix: | ||||
| case TagHandling.FullNameNoPrefix: | case TagHandling.FullNameNoPrefix: | ||||
| if (channel != null) | if (channel != null) | ||||
| return $"{channel.Name}"; | return $"{channel.Name}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
| return MentionChannel($"{SanitizeChar}{tag.Key}"); | return MentionChannel($"{SanitizeChar}{tag.Key}"); | ||||
| } | } | ||||
| @@ -225,13 +228,13 @@ namespace Discord | |||||
| if (role != null) | if (role != null) | ||||
| return $"@{role.Name}"; | return $"@{role.Name}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.NameNoPrefix: | case TagHandling.NameNoPrefix: | ||||
| case TagHandling.FullNameNoPrefix: | case TagHandling.FullNameNoPrefix: | ||||
| if (role != null) | if (role != null) | ||||
| return $"{role.Name}"; | return $"{role.Name}"; | ||||
| else | else | ||||
| return $""; | |||||
| return ""; | |||||
| case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
| return MentionRole($"{SanitizeChar}{tag.Key}"); | return MentionRole($"{SanitizeChar}{tag.Key}"); | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -86,7 +86,7 @@ namespace Discord | |||||
| private static ArgumentException CreateNotEqualException<T>(string name, string msg, T value) | private static ArgumentException CreateNotEqualException<T>(string name, string msg, T value) | ||||
| { | { | ||||
| if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); | |||||
| if (msg == null) return new ArgumentException($"Value may not be equal to {value}.", name); | |||||
| else return new ArgumentException(msg, name); | else return new ArgumentException(msg, name); | ||||
| } | } | ||||
| @@ -109,7 +109,7 @@ namespace Discord | |||||
| private static ArgumentException CreateAtLeastException<T>(string name, string msg, T value) | private static ArgumentException CreateAtLeastException<T>(string name, string msg, T value) | ||||
| { | { | ||||
| if (msg == null) return new ArgumentException($"Value must be at least {value}", name); | |||||
| if (msg == null) return new ArgumentException($"Value must be at least {value}.", name); | |||||
| else return new ArgumentException(msg, name); | else return new ArgumentException(msg, name); | ||||
| } | } | ||||
| @@ -132,7 +132,7 @@ namespace Discord | |||||
| private static ArgumentException CreateGreaterThanException<T>(string name, string msg, T value) | private static ArgumentException CreateGreaterThanException<T>(string name, string msg, T value) | ||||
| { | { | ||||
| if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); | |||||
| if (msg == null) return new ArgumentException($"Value must be greater than {value}.", name); | |||||
| else return new ArgumentException(msg, name); | else return new ArgumentException(msg, name); | ||||
| } | } | ||||
| @@ -155,7 +155,7 @@ namespace Discord | |||||
| private static ArgumentException CreateAtMostException<T>(string name, string msg, T value) | private static ArgumentException CreateAtMostException<T>(string name, string msg, T value) | ||||
| { | { | ||||
| if (msg == null) return new ArgumentException($"Value must be at most {value}", name); | |||||
| if (msg == null) return new ArgumentException($"Value must be at most {value}.", name); | |||||
| else return new ArgumentException(msg, name); | else return new ArgumentException(msg, name); | ||||
| } | } | ||||
| @@ -178,11 +178,12 @@ namespace Discord | |||||
| private static ArgumentException CreateLessThanException<T>(string name, string msg, T value) | private static ArgumentException CreateLessThanException<T>(string name, string msg, T value) | ||||
| { | { | ||||
| if (msg == null) return new ArgumentException($"Value must be less than {value}", name); | |||||
| if (msg == null) return new ArgumentException($"Value must be less than {value}.", name); | |||||
| else return new ArgumentException(msg, name); | else return new ArgumentException(msg, name); | ||||
| } | } | ||||
| // Bulk Delete | // Bulk Delete | ||||
| /// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks..</exception> | |||||
| public static void YoungerThanTwoWeeks(ulong[] collection, string name) | public static void YoungerThanTwoWeeks(ulong[] collection, string name) | ||||
| { | { | ||||
| var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); | var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); | ||||
| @@ -193,12 +194,13 @@ namespace Discord | |||||
| throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | 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> | |||||
| public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) | public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) | ||||
| { | { | ||||
| for (var i = 0; i < roles.Length; i++) | for (var i = 0; i < roles.Length; i++) | ||||
| { | { | ||||
| if (roles[i] == guildId) | if (roles[i] == guildId) | ||||
| throw new ArgumentException($"The everyone role cannot be assigned to a user", name); | |||||
| throw new ArgumentException("The everyone role cannot be assigned to a user.", name); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -24,11 +24,20 @@ namespace Discord.Rest | |||||
| internal API.DiscordRestApiClient ApiClient { get; } | internal API.DiscordRestApiClient ApiClient { get; } | ||||
| internal LogManager LogManager { get; } | internal LogManager LogManager { get; } | ||||
| /// <summary> | |||||
| /// Gets the login state of the client. | |||||
| /// </summary> | |||||
| public LoginState LoginState { get; private set; } | public LoginState LoginState { get; private set; } | ||||
| /// <summary> | |||||
| /// Gets the logged-in user. | |||||
| /// </summary> | |||||
| public ISelfUser CurrentUser { get; protected set; } | public ISelfUser CurrentUser { get; protected set; } | ||||
| /// <summary> | |||||
| /// Gets the type of the authentication token. | |||||
| /// </summary> | |||||
| public TokenType TokenType => ApiClient.AuthTokenType; | public TokenType TokenType => ApiClient.AuthTokenType; | ||||
| /// <summary> Creates a new REST-only discord client. </summary> | |||||
| /// <summary> Creates a new REST-only Discord client. </summary> | |||||
| internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) | internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) | ||||
| { | { | ||||
| ApiClient = client; | ApiClient = client; | ||||
| @@ -48,8 +57,7 @@ namespace Discord.Rest | |||||
| }; | }; | ||||
| ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | ||||
| { | { | ||||
| await _stateLock.WaitAsync().ConfigureAwait(false); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -87,8 +95,7 @@ namespace Discord.Rest | |||||
| } | } | ||||
| internal virtual Task OnLoginAsync(TokenType tokenType, string token) | internal virtual Task OnLoginAsync(TokenType tokenType, string token) | ||||
| => Task.Delay(0); | => Task.Delay(0); | ||||
| /// <inheritdoc /> | |||||
| public async Task LogoutAsync() | public async Task LogoutAsync() | ||||
| { | { | ||||
| await _stateLock.WaitAsync().ConfigureAwait(false); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -130,49 +137,68 @@ namespace Discord.Rest | |||||
| => ClientHelper.GetRecommendShardCountAsync(this, options); | => ClientHelper.GetRecommendShardCountAsync(this, options); | ||||
| //IDiscordClient | //IDiscordClient | ||||
| /// <inheritdoc /> | |||||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ||||
| /// <inheritdoc /> | |||||
| ISelfUser IDiscordClient.CurrentUser => CurrentUser; | ISelfUser IDiscordClient.CurrentUser => CurrentUser; | ||||
| /// <inheritdoc /> | |||||
| Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | ||||
| => throw new NotSupportedException(); | => throw new NotSupportedException(); | ||||
| /// <inheritdoc /> | |||||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IChannel>(null); | => Task.FromResult<IChannel>(null); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(ImmutableArray.Create<IPrivateChannel>()); | => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(ImmutableArray.Create<IPrivateChannel>()); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IDMChannel>>(ImmutableArray.Create<IDMChannel>()); | => Task.FromResult<IReadOnlyCollection<IDMChannel>>(ImmutableArray.Create<IDMChannel>()); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IGroupChannel>>(ImmutableArray.Create<IGroupChannel>()); | => Task.FromResult<IReadOnlyCollection<IGroupChannel>>(ImmutableArray.Create<IGroupChannel>()); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>()); | => Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>()); | ||||
| /// <inheritdoc /> | |||||
| Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | ||||
| => Task.FromResult<IInvite>(null); | => Task.FromResult<IInvite>(null); | ||||
| /// <inheritdoc /> | |||||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IGuild>(null); | => Task.FromResult<IGuild>(null); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>()); | => Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>()); | ||||
| /// <inheritdoc /> | |||||
| Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | ||||
| => throw new NotSupportedException(); | => throw new NotSupportedException(); | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IUser>(null); | => Task.FromResult<IUser>(null); | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
| => Task.FromResult<IUser>(null); | => Task.FromResult<IUser>(null); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(ImmutableArray.Create<IVoiceRegion>()); | => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(ImmutableArray.Create<IVoiceRegion>()); | ||||
| /// <inheritdoc /> | |||||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | ||||
| => Task.FromResult<IVoiceRegion>(null); | => Task.FromResult<IVoiceRegion>(null); | ||||
| /// <inheritdoc /> | |||||
| Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | ||||
| => Task.FromResult<IWebhook>(null); | => Task.FromResult<IWebhook>(null); | ||||
| /// <inheritdoc /> | |||||
| Task IDiscordClient.StartAsync() | Task IDiscordClient.StartAsync() | ||||
| => Task.Delay(0); | => Task.Delay(0); | ||||
| /// <inheritdoc /> | |||||
| Task IDiscordClient.StopAsync() | Task IDiscordClient.StopAsync() | ||||
| => Task.Delay(0); | => Task.Delay(0); | ||||
| } | } | ||||
| @@ -33,65 +33,49 @@ namespace Discord.Rest | |||||
| _applicationInfo = null; | _applicationInfo = null; | ||||
| return Task.Delay(0); | return Task.Delay(0); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | ||||
| { | { | ||||
| return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false)); | return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false)); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task<RestChannel> GetChannelAsync(ulong id, RequestOptions options = null) | public Task<RestChannel> GetChannelAsync(ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetChannelAsync(this, id, options); | => ClientHelper.GetChannelAsync(this, id, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetPrivateChannelsAsync(this, options); | => ClientHelper.GetPrivateChannelsAsync(this, options); | ||||
| public Task<IReadOnlyCollection<RestDMChannel>> GetDMChannelsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestDMChannel>> GetDMChannelsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetDMChannelsAsync(this, options); | => ClientHelper.GetDMChannelsAsync(this, options); | ||||
| public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetGroupChannelsAsync(this, options); | => ClientHelper.GetGroupChannelsAsync(this, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetConnectionsAsync(this, options); | => ClientHelper.GetConnectionsAsync(this, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | ||||
| => ClientHelper.GetInviteAsync(this, inviteId, options); | => ClientHelper.GetInviteAsync(this, inviteId, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null) | public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetGuildAsync(this, id, options); | => ClientHelper.GetGuildAsync(this, id, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null) | public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetGuildEmbedAsync(this, id, options); | => ClientHelper.GetGuildEmbedAsync(this, id, options); | ||||
| /// <inheritdoc /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetGuildSummariesAsync(this, null, null, options); | => ClientHelper.GetGuildSummariesAsync(this, null, null, options); | ||||
| /// <inheritdoc /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null) | ||||
| => ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options); | => ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetGuildsAsync(this, options); | => ClientHelper.GetGuildsAsync(this, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | ||||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options); | => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUser> GetUserAsync(ulong id, RequestOptions options = null) | public Task<RestUser> GetUserAsync(ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetUserAsync(this, id, options); | => ClientHelper.GetUserAsync(this, id, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestGuildUser> GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null) | public Task<RestGuildUser> GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetGuildUserAsync(this, guildId, id, options); | => ClientHelper.GetGuildUserAsync(this, guildId, id, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetVoiceRegionsAsync(this, options); | => ClientHelper.GetVoiceRegionsAsync(this, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | ||||
| => ClientHelper.GetVoiceRegionAsync(this, id, options); | => ClientHelper.GetVoiceRegionAsync(this, id, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetWebhookAsync(this, id, options); | => ClientHelper.GetWebhookAsync(this, id, options); | ||||
| @@ -1,7 +1,10 @@ | |||||
| using Discord.Net.Rest; | |||||
| using Discord.Net.Rest; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a configuration class for <see cref="DiscordRestClient" />. | |||||
| /// </summary> | |||||
| public class DiscordRestConfig : DiscordConfig | public class DiscordRestConfig : DiscordConfig | ||||
| { | { | ||||
| /// <summary> Gets or sets the provider used to generate new REST connections. </summary> | /// <summary> Gets or sets the provider used to generate new REST connections. </summary> | ||||
| @@ -288,7 +288,7 @@ namespace Discord.Rest | |||||
| } | } | ||||
| public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, | public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, | ||||
| RequestOptions options) | RequestOptions options) | ||||
| => new TypingNotifier(client, channel, options); | |||||
| => new TypingNotifier(channel, options); | |||||
| //Webhooks | //Webhooks | ||||
| public static async Task<RestWebhook> CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options) | public static async Task<RestWebhook> CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options) | ||||
| @@ -3,7 +3,7 @@ using Model = Discord.API.InviteMetadata; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| /// <summary> Represents additional information regarding the REST invite object. </summary> | |||||
| /// <summary> Represents additional information regarding the REST-based invite object. </summary> | |||||
| public class RestInviteMetadata : RestInvite, IInviteMetadata | public class RestInviteMetadata : RestInvite, IInviteMetadata | ||||
| { | { | ||||
| private long _createdAtTicks; | private long _createdAtTicks; | ||||
| @@ -48,6 +48,7 @@ namespace Discord.Rest | |||||
| _createdAtTicks = model.CreatedAt.UtcTicks; | _createdAtTicks = model.CreatedAt.UtcTicks; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| IUser IInviteMetadata.Inviter => Inviter; | IUser IInviteMetadata.Inviter => Inviter; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.IO; | using System.IO; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Model = Discord.API.Image; | using Model = Discord.API.Image; | ||||
| @@ -13,6 +13,7 @@ namespace Discord.Net.Converters | |||||
| public override bool CanRead => true; | public override bool CanRead => true; | ||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| /// <exception cref="InvalidOperationException">Cannot read from image.</exception> | |||||
| public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | ||||
| { | { | ||||
| throw new InvalidOperationException(); | throw new InvalidOperationException(); | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Newtonsoft.Json; | |||||
| using Newtonsoft.Json; | |||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | namespace Discord.Net.Converters | ||||
| @@ -11,6 +11,7 @@ namespace Discord.Net.Converters | |||||
| public override bool CanRead => true; | public override bool CanRead => true; | ||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| /// <exception cref="JsonSerializationException">Unknown permission target.</exception> | |||||
| public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | ||||
| { | { | ||||
| switch ((string)reader.Value) | switch ((string)reader.Value) | ||||
| @@ -20,10 +21,11 @@ namespace Discord.Net.Converters | |||||
| case "role": | case "role": | ||||
| return PermissionTarget.Role; | return PermissionTarget.Role; | ||||
| default: | default: | ||||
| throw new JsonSerializationException("Unknown permission target"); | |||||
| throw new JsonSerializationException("Unknown permission target."); | |||||
| } | } | ||||
| } | } | ||||
| /// <exception cref="JsonSerializationException">Invalid permission target.</exception> | |||||
| public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | ||||
| { | { | ||||
| switch ((PermissionTarget)value) | switch ((PermissionTarget)value) | ||||
| @@ -35,7 +37,7 @@ namespace Discord.Net.Converters | |||||
| writer.WriteValue("role"); | writer.WriteValue("role"); | ||||
| break; | break; | ||||
| default: | default: | ||||
| throw new JsonSerializationException("Invalid permission target"); | |||||
| throw new JsonSerializationException("Invalid permission target."); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| #if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -117,7 +117,7 @@ namespace Discord.Net.Queue | |||||
| if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) | if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) | ||||
| _buckets.TryRemove(bucket.Id, out RequestBucket ignored); | _buckets.TryRemove(bucket.Id, out RequestBucket ignored); | ||||
| } | } | ||||
| await Task.Delay(60000, _cancelToken.Token); //Runs each minute | |||||
| await Task.Delay(60000, _cancelToken.Token).ConfigureAwait(false); //Runs each minute | |||||
| } | } | ||||
| } | } | ||||
| catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -6,21 +6,19 @@ namespace Discord.Rest | |||||
| { | { | ||||
| internal class TypingNotifier : IDisposable | internal class TypingNotifier : IDisposable | ||||
| { | { | ||||
| private readonly BaseDiscordClient _client; | |||||
| private readonly CancellationTokenSource _cancelToken; | private readonly CancellationTokenSource _cancelToken; | ||||
| private readonly IMessageChannel _channel; | private readonly IMessageChannel _channel; | ||||
| private readonly RequestOptions _options; | private readonly RequestOptions _options; | ||||
| public TypingNotifier(BaseDiscordClient discord, IMessageChannel channel, RequestOptions options) | |||||
| public TypingNotifier(IMessageChannel channel, RequestOptions options) | |||||
| { | { | ||||
| _client = discord; | |||||
| _cancelToken = new CancellationTokenSource(); | _cancelToken = new CancellationTokenSource(); | ||||
| _channel = channel; | _channel = channel; | ||||
| _options = options; | _options = options; | ||||
| var _ = Run(); | |||||
| _ = RunAsync(); | |||||
| } | } | ||||
| private async Task Run() | |||||
| private async Task RunAsync() | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -31,7 +29,11 @@ namespace Discord.Rest | |||||
| { | { | ||||
| await _channel.TriggerTypingAsync(_options).ConfigureAwait(false); | await _channel.TriggerTypingAsync(_options).ConfigureAwait(false); | ||||
| } | } | ||||
| catch { } | |||||
| catch | |||||
| { | |||||
| // ignored | |||||
| } | |||||
| await Task.Delay(9500, token).ConfigureAwait(false); | await Task.Delay(9500, token).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.API.Voice; | |||||
| using Discord.API.Voice; | |||||
| using Discord.Audio.Streams; | using Discord.Audio.Streams; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| @@ -65,7 +65,7 @@ namespace Discord.Audio | |||||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | ||||
| ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ||||
| ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||||
| ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); | |||||
| //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | ||||
| ApiClient.ReceivedEvent += ProcessMessageAsync; | ApiClient.ReceivedEvent += ProcessMessageAsync; | ||||
| ApiClient.ReceivedPacket += ProcessPacketAsync; | ApiClient.ReceivedPacket += ProcessPacketAsync; | ||||
| @@ -291,7 +291,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| if (packet.Length != 70) | if (packet.Length != 70) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); | |||||
| await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); | |||||
| return; | return; | ||||
| } | } | ||||
| string ip; | string ip; | ||||
| @@ -303,7 +303,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); | |||||
| await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -343,7 +343,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) | if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); | |||||
| await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); | |||||
| return; | return; | ||||
| } | } | ||||
| if (!_ssrcMap.TryGetValue(ssrc, out var userId)) | if (!_ssrcMap.TryGetValue(ssrc, out var userId)) | ||||
| @@ -362,7 +362,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); | |||||
| await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); | |||||
| return; | return; | ||||
| } | } | ||||
| //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | ||||
| @@ -371,7 +371,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.WarningAsync($"Failed to process UDP packet", ex).ConfigureAwait(false); | |||||
| await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -116,7 +116,7 @@ namespace Discord.Audio.Streams | |||||
| timestamp += OpusEncoder.FrameSamplesPerChannel; | timestamp += OpusEncoder.FrameSamplesPerChannel; | ||||
| } | } | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync($"Buffer underrun"); | |||||
| var _ = _logger?.DebugAsync("Buffer underrun"); | |||||
| #endif | #endif | ||||
| } | } | ||||
| } | } | ||||
| @@ -140,7 +140,7 @@ namespace Discord.Audio.Streams | |||||
| if (!_bufferPool.TryDequeue(out byte[] buffer)) | if (!_bufferPool.TryDequeue(out byte[] buffer)) | ||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync($"Buffer overflow"); //Should never happen because of the queueLock | |||||
| var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock | |||||
| #endif | #endif | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -149,7 +149,7 @@ namespace Discord.Audio.Streams | |||||
| if (!_isPreloaded && _queuedFrames.Count == _queueLength) | if (!_isPreloaded && _queuedFrames.Count == _queueLength) | ||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync($"Preloaded"); | |||||
| var _ = _logger?.DebugAsync("Preloaded"); | |||||
| #endif | #endif | ||||
| _isPreloaded = true; | _isPreloaded = true; | ||||
| } | } | ||||
| @@ -173,4 +173,4 @@ namespace Discord.Audio.Streams | |||||
| return Task.Delay(0); | return Task.Delay(0); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -26,18 +26,12 @@ namespace Discord.WebSocket | |||||
| : base(config, client) => _baseconfig = config; | : base(config, client) => _baseconfig = config; | ||||
| private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
| => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | ||||
| /// <inheritdoc /> | |||||
| public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null); | public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null); | ||||
| /// <inheritdoc /> | |||||
| public abstract SocketUser GetUser(ulong id); | public abstract SocketUser GetUser(ulong id); | ||||
| /// <inheritdoc /> | |||||
| public abstract SocketUser GetUser(string username, string discriminator); | public abstract SocketUser GetUser(string username, string discriminator); | ||||
| /// <inheritdoc /> | |||||
| public abstract SocketChannel GetChannel(ulong id); | public abstract SocketChannel GetChannel(ulong id); | ||||
| /// <inheritdoc /> | |||||
| public abstract SocketGuild GetGuild(ulong id); | public abstract SocketGuild GetGuild(ulong id); | ||||
| /// <inheritdoc /> | |||||
| public abstract RestVoiceRegion GetVoiceRegion(string id); | public abstract RestVoiceRegion GetVoiceRegion(string id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public abstract Task StartAsync(); | public abstract Task StartAsync(); | ||||
| @@ -47,47 +41,56 @@ namespace Discord.WebSocket | |||||
| public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing); | public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing); | ||||
| public abstract Task SetActivityAsync(IActivity activity); | public abstract Task SetActivityAsync(IActivity activity); | ||||
| public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | ||||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); | => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); | => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | ||||
| => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | ||||
| // IDiscordClient | // IDiscordClient | ||||
| /// <inheritdoc /> | |||||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | ||||
| => await GetApplicationInfoAsync(options).ConfigureAwait(false); | => await GetApplicationInfoAsync(options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IChannel>(GetChannel(id)); | => Task.FromResult<IChannel>(GetChannel(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | ||||
| /// <inheritdoc /> | |||||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | ||||
| => await GetConnectionsAsync(options).ConfigureAwait(false); | => await GetConnectionsAsync(options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | ||||
| => await GetInviteAsync(inviteId, options).ConfigureAwait(false); | => await GetInviteAsync(inviteId, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IGuild>(GetGuild(id)); | => Task.FromResult<IGuild>(GetGuild(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | ||||
| /// <inheritdoc /> | |||||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | ||||
| => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
| /// <inheritdoc /> | |||||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | ||||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | ||||
| } | } | ||||
| @@ -5,20 +5,37 @@ namespace Discord.Commands | |||||
| /// <summary> The WebSocket variant of <see cref="ICommandContext"/>, which may contain the client, user, guild, channel, and message. </summary> | /// <summary> The WebSocket variant of <see cref="ICommandContext"/>, which may contain the client, user, guild, channel, and message. </summary> | ||||
| public class SocketCommandContext : ICommandContext | public class SocketCommandContext : ICommandContext | ||||
| { | { | ||||
| /// <summary> Gets the <see cref="DiscordSocketClient"/> that the command is executed with. </summary> | |||||
| /// <summary> | |||||
| /// Gets the <see cref="DiscordSocketClient" /> that the command is executed with. | |||||
| /// </summary> | |||||
| public DiscordSocketClient Client { get; } | public DiscordSocketClient Client { get; } | ||||
| /// <summary> Gets the <see cref="SocketGuild"/> that the command is executed in. </summary> | |||||
| /// <summary> | |||||
| /// Gets the <see cref="SocketGuild" /> that the command is executed in. | |||||
| /// </summary> | |||||
| public SocketGuild Guild { get; } | public SocketGuild Guild { get; } | ||||
| /// <summary> Gets the <see cref="ISocketMessageChannel"/> that the command is executed in. </summary> | |||||
| /// <summary> | |||||
| /// Gets the <see cref="ISocketMessageChannel" /> that the command is executed in. | |||||
| /// </summary> | |||||
| public ISocketMessageChannel Channel { get; } | public ISocketMessageChannel Channel { get; } | ||||
| /// <summary> Gets the <see cref="SocketUser"/> who executed the command. </summary> | |||||
| /// <summary> | |||||
| /// Gets the <see cref="SocketUser" /> who executed the command. | |||||
| /// </summary> | |||||
| public SocketUser User { get; } | public SocketUser User { get; } | ||||
| /// <summary> Gets the <see cref="SocketUserMessage"/> that the command is interpreted from. </summary> | |||||
| /// <summary> | |||||
| /// Gets the <see cref="SocketUserMessage" /> that the command is interpreted from. | |||||
| /// </summary> | |||||
| public SocketUserMessage Message { get; } | public SocketUserMessage Message { get; } | ||||
| /// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary> | |||||
| /// <summary> | |||||
| /// Indicates whether the channel that the command is executed in is a private channel. | |||||
| /// </summary> | |||||
| public bool IsPrivate => Channel is IPrivateChannel; | public bool IsPrivate => Channel is IPrivateChannel; | ||||
| /// <summary> | |||||
| /// Initializes a new <see cref="SocketCommandContext" /> class with the provided client and message. | |||||
| /// </summary> | |||||
| /// <param name="client">The underlying client.</param> | |||||
| /// <param name="msg">The underlying message.</param> | |||||
| public SocketCommandContext(DiscordSocketClient client, SocketUserMessage msg) | public SocketCommandContext(DiscordSocketClient client, SocketUserMessage msg) | ||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| @@ -19,24 +19,29 @@ namespace Discord.WebSocket | |||||
| private int _totalShards; | private int _totalShards; | ||||
| private bool _automaticShards; | private bool _automaticShards; | ||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||||
| /// <inheritdoc /> | |||||
| public override int Latency { get => GetLatency(); protected set { } } | public override int Latency { get => GetLatency(); protected set { } } | ||||
| /// <inheritdoc /> | |||||
| public override UserStatus Status { get => _shards[0].Status; protected set { } } | public override UserStatus Status { get => _shards[0].Status; protected set { } } | ||||
| /// <inheritdoc /> | |||||
| public override IActivity Activity { get => _shards[0].Activity; protected set { } } | public override IActivity Activity { get => _shards[0].Activity; protected set { } } | ||||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
| public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | |||||
| public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | |||||
| /// <inheritdoc /> | |||||
| public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); | |||||
| /// <inheritdoc /> | |||||
| public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | |||||
| public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | ||||
| /// <inheritdoc /> | |||||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | |||||
| /// <summary> Creates a new REST/WebSocket Discord client. </summary> | |||||
| public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | |||||
| /// <summary> Creates a new REST/WebSocket Discord client. </summary> | |||||
| public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { } | public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | |||||
| /// <summary> Creates a new REST/WebSocket Discord client. </summary> | |||||
| public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } | public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | |||||
| /// <summary> Creates a new REST/WebSocket Discord client. </summary> | |||||
| public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { } | public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { } | ||||
| private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client) | private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client) | ||||
| : base(config, client) | : base(config, client) | ||||
| @@ -213,7 +218,9 @@ namespace Discord.WebSocket | |||||
| public override RestVoiceRegion GetVoiceRegion(string id) | public override RestVoiceRegion GetVoiceRegion(string id) | ||||
| => _shards[0].GetVoiceRegion(id); | => _shards[0].GetVoiceRegion(id); | ||||
| /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | |||||
| /// <summary> | |||||
| /// Downloads the users list for the provided guilds if they don't have a complete list. | |||||
| /// </summary> | |||||
| public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | ||||
| { | { | ||||
| for (int i = 0; i < _shards.Length; i++) | for (int i = 0; i < _shards.Length; i++) | ||||
| @@ -233,11 +240,13 @@ namespace Discord.WebSocket | |||||
| return (int)Math.Round(total / (double)_shards.Length); | return (int)Math.Round(total / (double)_shards.Length); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public override async Task SetStatusAsync(UserStatus status) | public override async Task SetStatusAsync(UserStatus status) | ||||
| { | { | ||||
| for (int i = 0; i < _shards.Length; i++) | for (int i = 0; i < _shards.Length; i++) | ||||
| await _shards[i].SetStatusAsync(status).ConfigureAwait(false); | await _shards[i].SetStatusAsync(status).ConfigureAwait(false); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) | public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) | ||||
| { | { | ||||
| IActivity activity = null; | IActivity activity = null; | ||||
| @@ -247,6 +256,7 @@ namespace Discord.WebSocket | |||||
| activity = new Game(name, type); | activity = new Game(name, type); | ||||
| await SetActivityAsync(activity).ConfigureAwait(false); | await SetActivityAsync(activity).ConfigureAwait(false); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public override async Task SetActivityAsync(IActivity activity) | public override async Task SetActivityAsync(IActivity activity) | ||||
| { | { | ||||
| for (int i = 0; i < _shards.Length; i++) | for (int i = 0; i < _shards.Length; i++) | ||||
| @@ -316,34 +326,46 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| //IDiscordClient | //IDiscordClient | ||||
| /// <inheritdoc /> | |||||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | ||||
| => await GetApplicationInfoAsync().ConfigureAwait(false); | => await GetApplicationInfoAsync().ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IChannel>(GetChannel(id)); | => Task.FromResult<IChannel>(GetChannel(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | ||||
| /// <inheritdoc /> | |||||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | ||||
| => await GetConnectionsAsync().ConfigureAwait(false); | => await GetConnectionsAsync().ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | ||||
| => await GetInviteAsync(inviteId).ConfigureAwait(false); | => await GetInviteAsync(inviteId).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IGuild>(GetGuild(id)); | => Task.FromResult<IGuild>(GetGuild(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | ||||
| /// <inheritdoc /> | |||||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | ||||
| => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
| /// <inheritdoc /> | |||||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | ||||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | ||||
| /// <inheritdoc /> | |||||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | ||||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | ||||
| } | } | ||||
| @@ -508,7 +508,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| type = "GUILD_AVAILABLE"; | type = "GUILD_AVAILABLE"; | ||||
| _lastGuildAvailableTime = Environment.TickCount; | _lastGuildAvailableTime = Environment.TickCount; | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -533,7 +533,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||||
| var guild = AddGuild(data, State); | var guild = AddGuild(data, State); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -614,7 +614,7 @@ namespace Discord.WebSocket | |||||
| if (data.Unavailable == true) | if (data.Unavailable == true) | ||||
| { | { | ||||
| type = "GUILD_UNAVAILABLE"; | type = "GUILD_UNAVAILABLE"; | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -630,7 +630,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||||
| var guild = RemoveGuild(data.Id); | var guild = RemoveGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -1,41 +1,72 @@ | |||||
| using Discord.Net.Udp; | |||||
| using Discord.Net.Udp; | |||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a configuration class for <see cref="DiscordSocketClient" />. | |||||
| /// </summary> | |||||
| public class DiscordSocketConfig : DiscordRestConfig | public class DiscordSocketConfig : DiscordRestConfig | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Gets or sets the encoding gateway should use. | |||||
| /// </summary> | |||||
| public const string GatewayEncoding = "json"; | public const string GatewayEncoding = "json"; | ||||
| /// <summary> Gets or sets the websocket host to connect to. If null, the client will use the /gateway endpoint. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the WebSocket host to connect to. If <see langword="null"/>, the client will use the | |||||
| /// /gateway endpoint. | |||||
| /// </summary> | |||||
| public string GatewayHost { get; set; } = null; | public string GatewayHost { get; set; } = null; | ||||
| /// <summary> Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. | |||||
| /// </summary> | |||||
| public int ConnectionTimeout { get; set; } = 30000; | public int ConnectionTimeout { get; set; } = 30000; | ||||
| /// <summary> Gets or sets the id for this shard. Must be less than TotalShards. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the ID for this shard. Must be less than <see cref="TotalShards"/>. | |||||
| /// </summary> | |||||
| public int? ShardId { get; set; } = null; | public int? ShardId { get; set; } = null; | ||||
| /// <summary> Gets or sets the total number of shards for this application. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the total number of shards for this application. | |||||
| /// </summary> | |||||
| public int? TotalShards { get; set; } = null; | public int? TotalShards { get; set; } = null; | ||||
| /// <summary> Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero | |||||
| /// disables the message cache entirely. | |||||
| /// </summary> | |||||
| public int MessageCacheSize { get; set; } = 0; | public int MessageCacheSize { get; set; } = 0; | ||||
| /// <summary> | |||||
| /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. | |||||
| /// <summary> | |||||
| /// Gets or sets the max number of users a guild may have for offline users to be included in the READY | |||||
| /// packet. Max is 250. | |||||
| /// </summary> | /// </summary> | ||||
| public int LargeThreshold { get; set; } = 250; | public int LargeThreshold { get; set; } = 250; | ||||
| /// <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 WebSocketProvider WebSocketProvider { get; set; } | ||||
| /// <summary> Gets or sets the provider used to generate new udp sockets. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the provider used to generate new UDP sockets. | |||||
| /// </summary> | |||||
| public UdpSocketProvider UdpSocketProvider { get; set; } | public UdpSocketProvider UdpSocketProvider { get; set; } | ||||
| /// <summary> Gets or sets whether or not all users should be downloaded as guilds come available. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets whether or not all users should be downloaded as guilds come available. | |||||
| /// </summary> | |||||
| public bool AlwaysDownloadUsers { get; set; } = false; | public bool AlwaysDownloadUsers { get; set; } = false; | ||||
| /// <summary> Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. Null disables this check. </summary> | |||||
| /// <summary> | |||||
| /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. Null | |||||
| /// disables this check. | |||||
| /// </summary> | |||||
| public int? HandlerTimeout { get; set; } = 3000; | public int? HandlerTimeout { get; set; } = 3000; | ||||
| /// <summary> | |||||
| /// Initializes a default configuration. | |||||
| /// </summary> | |||||
| public DiscordSocketConfig() | public DiscordSocketConfig() | ||||
| { | { | ||||
| WebSocketProvider = DefaultWebSocketProvider.Instance; | WebSocketProvider = DefaultWebSocketProvider.Instance; | ||||
| @@ -1,5 +1,8 @@ | |||||
| namespace Discord.WebSocket | |||||
| namespace Discord.WebSocket | |||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a generic WebSocket-based audio channel. | |||||
| /// </summary> | |||||
| public interface ISocketAudioChannel : IAudioChannel | public interface ISocketAudioChannel : IAudioChannel | ||||
| { | { | ||||
| } | } | ||||
| @@ -5,18 +5,52 @@ using System.Threading.Tasks; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a generic WebSocket-based channel that can send and receive messages. | |||||
| /// </summary> | |||||
| public interface ISocketMessageChannel : IMessageChannel | public interface ISocketMessageChannel : IMessageChannel | ||||
| { | { | ||||
| /// <summary> Gets all messages in this channel's cache. </summary> | /// <summary> Gets all messages in this channel's cache. </summary> | ||||
| IReadOnlyCollection<SocketMessage> CachedMessages { get; } | IReadOnlyCollection<SocketMessage> CachedMessages { get; } | ||||
| /// <summary> Sends a message to this message channel. </summary> | |||||
| /// <summary> | |||||
| /// Sends a message to this message channel. | |||||
| /// </summary> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); | new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); | ||||
| #if FILESYSTEM | #if FILESYSTEM | ||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||||
| /// <summary> | |||||
| /// Sends a file to this message channel, with an optional caption. | |||||
| /// </summary> | |||||
| /// <param name="filePath">The file path of the file.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <remarks> | |||||
| /// If you wish to upload an image and have it embedded in a <see cref="EmbedType.Rich"/> embed, you may | |||||
| /// upload the file and refer to the file with "attachment://filename.ext" in the | |||||
| /// <see cref="Discord.EmbedBuilder.ImageUrl"/>. | |||||
| /// </remarks> | |||||
| new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | ||||
| #endif | #endif | ||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||||
| /// <summary> | |||||
| /// Sends a file to this message channel, with an optional caption. | |||||
| /// </summary> | |||||
| /// <param name="stream">The <see cref="Stream"/> of the file to be sent.</param> | |||||
| /// <param name="filename">The name of the attachment.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <remarks> | |||||
| /// If you wish to upload an image and have it embedded in a <see cref="EmbedType.Rich"/> embed, you may | |||||
| /// upload the file and refer to the file with "attachment://filename.ext" in the | |||||
| /// <see cref="Discord.EmbedBuilder.ImageUrl"/>. | |||||
| /// </remarks> | |||||
| new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | ||||
| SocketMessage GetCachedMessage(ulong id); | SocketMessage GetCachedMessage(ulong id); | ||||
| @@ -26,7 +60,13 @@ namespace Discord.WebSocket | |||||
| IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | ||||
| /// <summary> Gets a collection of messages in this channel. </summary> | /// <summary> Gets a collection of messages in this channel. </summary> | ||||
| IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | ||||
| /// <summary> Gets a collection of pinned messages in this channel. </summary> | |||||
| /// <summary> | |||||
| /// Gets a collection of pinned messages in this channel. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of messages. | |||||
| /// </returns> | |||||
| new Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | new Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,10 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a generic WebSocket-based channel that is private to select recipients. | |||||
| /// </summary> | |||||
| public interface ISocketPrivateChannel : IPrivateChannel | public interface ISocketPrivateChannel : IPrivateChannel | ||||
| { | { | ||||
| new IReadOnlyCollection<SocketUser> Recipients { get; } | new IReadOnlyCollection<SocketUser> Recipients { get; } | ||||
| @@ -3,14 +3,14 @@ using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Text; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Audio; | |||||
| using Discord.Rest; | |||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a WebSocket-based category channel. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel | public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel | ||||
| { | { | ||||
| @@ -1,4 +1,3 @@ | |||||
| using Discord.Rest; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -8,16 +7,27 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a WebSocket-based channel. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public abstract class SocketChannel : SocketEntity<ulong>, IChannel | public abstract class SocketChannel : SocketEntity<ulong>, IChannel | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Gets when the channel is created. | |||||
| /// </summary> | |||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
| /// <summary> | |||||
| /// Gets a collection of users from the WebSocket cache. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketUser> Users => GetUsersInternal(); | public IReadOnlyCollection<SocketUser> Users => GetUsersInternal(); | ||||
| internal SocketChannel(DiscordSocketClient discord, ulong id) | internal SocketChannel(DiscordSocketClient discord, ulong id) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| /// <exception cref="InvalidOperationException">Unexpected channel type is created.</exception> | |||||
| internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) | internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) | ||||
| { | { | ||||
| switch (model.Type) | switch (model.Type) | ||||
| @@ -33,6 +43,17 @@ namespace Discord.WebSocket | |||||
| internal abstract void Update(ClientState state, Model model); | internal abstract void Update(ClientState state, Model model); | ||||
| //User | //User | ||||
| /// <summary> | |||||
| /// Gets the user from the WebSocket cache. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This method does NOT attempt to fetch the user if they don't exist in the cache. To guarantee a return | |||||
| /// from an existing user that doesn't exist in cache, use <see cref="Rest.DiscordRestClient.GetUserAsync" />. | |||||
| /// </remarks> | |||||
| /// <param name="id">The ID of the user.</param> | |||||
| /// <returns> | |||||
| /// The user. | |||||
| /// </returns> | |||||
| public SocketUser GetUser(ulong id) => GetUserInternal(id); | public SocketUser GetUser(ulong id) => GetUserInternal(id); | ||||
| internal abstract SocketUser GetUserInternal(ulong id); | internal abstract SocketUser GetUserInternal(ulong id); | ||||
| internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal(); | internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal(); | ||||
| @@ -40,10 +61,13 @@ namespace Discord.WebSocket | |||||
| internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; | internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; | ||||
| //IChannel | //IChannel | ||||
| /// <inheritdoc /> | |||||
| string IChannel.Name => null; | string IChannel.Name => null; | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IUser>(null); //Overridden | => Task.FromResult<IUser>(null); //Overridden | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden | => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden | ||||
| } | } | ||||
| @@ -10,13 +10,17 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a WebSocket-based direct-message channel. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | ||||
| { | { | ||||
| private readonly MessageCache _messages; | private readonly MessageCache _messages; | ||||
| public SocketUser Recipient { get; private set; } | |||||
| public SocketUser Recipient { get; } | |||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | ||||
| public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | ||||
| @@ -39,10 +43,12 @@ namespace Discord.WebSocket | |||||
| Recipient.Update(state, model.Recipients.Value[0]); | Recipient.Update(state, model.Recipients.Value[0]); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task CloseAsync(RequestOptions options = null) | public Task CloseAsync(RequestOptions options = null) | ||||
| => ChannelHelper.DeleteAsync(this, Discord, options); | => ChannelHelper.DeleteAsync(this, Discord, options); | ||||
| //Messages | //Messages | ||||
| /// <inheritdoc /> | |||||
| public SocketMessage GetCachedMessage(ulong id) | public SocketMessage GetCachedMessage(ulong id) | ||||
| => _messages?.Get(id); | => _messages?.Get(id); | ||||
| public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | ||||
| @@ -58,26 +64,35 @@ namespace Discord.WebSocket | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | ||||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | ||||
| #if FILESYSTEM | #if FILESYSTEM | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | ||||
| #endif | #endif | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | ||||
| /// <inheritdoc /> | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| /// <inheritdoc /> | |||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| => ChannelHelper.EnterTypingState(this, Discord, options); | => ChannelHelper.EnterTypingState(this, Discord, options); | ||||
| @@ -97,24 +112,33 @@ namespace Discord.WebSocket | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Returns the recipient user. | |||||
| /// </summary> | |||||
| public override string ToString() => $"@{Recipient}"; | public override string ToString() => $"@{Recipient}"; | ||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | ||||
| internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | ||||
| //SocketChannel | //SocketChannel | ||||
| /// <inheritdoc /> | |||||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | ||||
| /// <inheritdoc /> | |||||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | ||||
| //IDMChannel | |||||
| //IDMChannel | |||||
| /// <inheritdoc /> | |||||
| IUser IDMChannel.Recipient => Recipient; | IUser IDMChannel.Recipient => Recipient; | ||||
| //ISocketPrivateChannel | //ISocketPrivateChannel | ||||
| /// <inheritdoc /> | |||||
| IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | ||||
| //IPrivateChannel | //IPrivateChannel | ||||
| /// <inheritdoc /> | |||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | ||||
| //IMessageChannel | //IMessageChannel | ||||
| /// <inheritdoc /> | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| { | { | ||||
| if (mode == CacheMode.AllowDownload) | if (mode == CacheMode.AllowDownload) | ||||
| @@ -122,30 +146,41 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| return GetCachedMessage(id); | return GetCachedMessage(id); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | ||||
| /// <inheritdoc /> | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
| => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
| #if FILESYSTEM | #if FILESYSTEM | ||||
| /// <inheritdoc /> | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | ||||
| => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | ||||
| #endif | #endif | ||||
| /// <inheritdoc /> | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | 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); | => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | ||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
| => EnterTypingState(options); | => EnterTypingState(options); | ||||
| //IChannel | |||||
| //IChannel | |||||
| /// <inheritdoc /> | |||||
| string IChannel.Name => $"@{Recipient}"; | string IChannel.Name => $"@{Recipient}"; | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ using VoiceStateModel = Discord.API.VoiceState; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a private WebSocket group channel. | |||||
| /// Represents a WebSocket-based private group channel. | |||||
| /// </summary> | /// </summary> | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | ||||
| @@ -24,10 +24,12 @@ namespace Discord.WebSocket | |||||
| private string _iconId; | private string _iconId; | ||||
| private ConcurrentDictionary<ulong, SocketGroupUser> _users; | private ConcurrentDictionary<ulong, SocketGroupUser> _users; | ||||
| private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||||
| private readonly ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||||
| /// <inheritdoc /> | |||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | ||||
| public new IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection(); | public new IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection(); | ||||
| public IReadOnlyCollection<SocketGroupUser> Recipients | public IReadOnlyCollection<SocketGroupUser> Recipients | ||||
| @@ -65,15 +67,18 @@ namespace Discord.WebSocket | |||||
| _users = users; | _users = users; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task LeaveAsync(RequestOptions options = null) | public Task LeaveAsync(RequestOptions options = null) | ||||
| => ChannelHelper.DeleteAsync(this, Discord, options); | => ChannelHelper.DeleteAsync(this, Discord, options); | ||||
| /// <exception cref="NotSupportedException">Voice is not yet supported for group channels.</exception> | |||||
| public Task<IAudioClient> ConnectAsync() | public Task<IAudioClient> ConnectAsync() | ||||
| { | { | ||||
| throw new NotSupportedException("Voice is not yet supported for group channels."); | throw new NotSupportedException("Voice is not yet supported for group channels."); | ||||
| } | } | ||||
| //Messages | //Messages | ||||
| /// <inheritdoc /> | |||||
| public SocketMessage GetCachedMessage(ulong id) | public SocketMessage GetCachedMessage(ulong id) | ||||
| => _messages?.Get(id); | => _messages?.Get(id); | ||||
| public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | ||||
| @@ -89,26 +94,35 @@ namespace Discord.WebSocket | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | ||||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | ||||
| #if FILESYSTEM | #if FILESYSTEM | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | ||||
| #endif | #endif | ||||
| /// <inheritdoc /> | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | ||||
| /// <inheritdoc /> | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| /// <inheritdoc /> | |||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| => ChannelHelper.EnterTypingState(this, Discord, options); | => ChannelHelper.EnterTypingState(this, Discord, options); | ||||
| @@ -118,6 +132,17 @@ namespace Discord.WebSocket | |||||
| => _messages?.Remove(id); | => _messages?.Remove(id); | ||||
| //Users | //Users | ||||
| /// <summary> | |||||
| /// Gets the group user from the WebSocket cache. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This method does NOT attempt to fetch the user if they don't exist in the cache. To guarantee a return | |||||
| /// from an existing user that doesn't exist in cache, use <see cref="DiscordRestClient.GetUserAsync" />. | |||||
| /// </remarks> | |||||
| /// <param name="id">The ID of the user.</param> | |||||
| /// <returns> | |||||
| /// The user in the group. | |||||
| /// </returns> | |||||
| public new SocketGroupUser GetUser(ulong id) | public new SocketGroupUser GetUser(ulong id) | ||||
| { | { | ||||
| if (_users.TryGetValue(id, out SocketGroupUser user)) | if (_users.TryGetValue(id, out SocketGroupUser user)) | ||||
| @@ -167,21 +192,29 @@ namespace Discord.WebSocket | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Returns the name of the group. | |||||
| /// </summary> | |||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| private string DebuggerDisplay => $"{Name} ({Id}, Group)"; | private string DebuggerDisplay => $"{Name} ({Id}, Group)"; | ||||
| internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; | internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; | ||||
| //SocketChannel | //SocketChannel | ||||
| /// <inheritdoc /> | |||||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | ||||
| /// <inheritdoc /> | |||||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | ||||
| //ISocketPrivateChannel | //ISocketPrivateChannel | ||||
| /// <inheritdoc /> | |||||
| IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => Recipients; | IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => Recipients; | ||||
| //IPrivateChannel | //IPrivateChannel | ||||
| /// <inheritdoc /> | |||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | ||||
| //IMessageChannel | //IMessageChannel | ||||
| /// <inheritdoc /> | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| { | { | ||||
| if (mode == CacheMode.AllowDownload) | if (mode == CacheMode.AllowDownload) | ||||
| @@ -189,31 +222,42 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| return GetCachedMessage(id); | return GetCachedMessage(id); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | ||||
| /// <inheritdoc /> | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
| => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
| #if FILESYSTEM | #if FILESYSTEM | ||||
| /// <inheritdoc /> | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | ||||
| => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | ||||
| #endif | #endif | ||||
| /// <inheritdoc /> | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | 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); | => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | ||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
| => EnterTypingState(options); | => EnterTypingState(options); | ||||
| //IAudioChannel | //IAudioChannel | ||||
| /// <inheritdoc /> | |||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(Action<IAudioClient> configAction) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(Action<IAudioClient> configAction) { throw new NotSupportedException(); } | ||||
| //IChannel | //IChannel | ||||
| /// <inheritdoc /> | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IUser>(GetUser(id)); | => Task.FromResult<IUser>(GetUser(id)); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | ||||
| } | } | ||||
| @@ -9,12 +9,20 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> The WebSocket variant of <see cref="IGuildChannel"/>. Represents a guild channel (text, voice, category). </summary> | |||||
| /// <summary> | |||||
| /// Represents a WebSocket-based guild channel. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketGuildChannel : SocketChannel, IGuildChannel | public class SocketGuildChannel : SocketChannel, IGuildChannel | ||||
| { | { | ||||
| private ImmutableArray<Overwrite> _overwrites; | private ImmutableArray<Overwrite> _overwrites; | ||||
| /// <summary> | |||||
| /// Gets the guild associated with this channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The guild that this channel belongs to. | |||||
| /// </returns> | |||||
| public SocketGuild Guild { get; } | public SocketGuild Guild { get; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| @@ -22,11 +30,23 @@ namespace Discord.WebSocket | |||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ulong? CategoryId { get; private set; } | public ulong? CategoryId { get; private set; } | ||||
| /// <summary> | |||||
| /// Gets the parent category of this channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The parent category ID associated with this channel, or <see langword="null"/> if none is set. | |||||
| /// </returns> | |||||
| public ICategoryChannel Category | public ICategoryChannel Category | ||||
| => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; | => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | ||||
| /// <summary> | |||||
| /// Returns a collection of users that are able to view the channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A collection of users that can access the channel (i.e. the users seen in the user list). | |||||
| /// </returns> | |||||
| public new virtual IReadOnlyCollection<SocketGuildUser> Users => ImmutableArray.Create<SocketGuildUser>(); | public new virtual IReadOnlyCollection<SocketGuildUser> Users => ImmutableArray.Create<SocketGuildUser>(); | ||||
| internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | ||||
| @@ -131,7 +151,11 @@ namespace Discord.WebSocket | |||||
| public new virtual SocketGuildUser GetUser(ulong id) => null; | public new virtual SocketGuildUser GetUser(ulong id) => null; | ||||
| /// <summary> | |||||
| /// Gets the name of the channel. | |||||
| /// </summary> | |||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| private string DebuggerDisplay => $"{Name} ({Id}, Guild)"; | |||||
| internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; | internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; | ||||
| //SocketChannel | //SocketChannel | ||||
| @@ -10,6 +10,9 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a WebSocket-based channel in a guild that can send and receive messages. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel | public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel | ||||
| { | { | ||||
| @@ -73,12 +76,16 @@ namespace Discord.WebSocket | |||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | ||||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | ||||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | ||||
| /// <inheritdoc /> | |||||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | ||||
| /// <inheritdoc /> | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
| @@ -104,6 +111,7 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| /// <inheritdoc /> | |||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| => ChannelHelper.EnterTypingState(this, Discord, options); | => ChannelHelper.EnterTypingState(this, Discord, options); | ||||
| @@ -128,10 +136,34 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| //Webhooks | //Webhooks | ||||
| /// <summary> | |||||
| /// Creates a webhook in this text channel. | |||||
| /// </summary> | |||||
| /// <param name="name">The name of the webhook.</param> | |||||
| /// <param name="avatar">The avatar of the webhook.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// The created webhook. | |||||
| /// </returns> | |||||
| public Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) | public Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) | ||||
| => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); | => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); | ||||
| /// <summary> | |||||
| /// Gets the webhook in this text channel with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The ID of the webhook.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A webhook associated with the <paramref name="id"/>, or <see langword="null"/> if not found. | |||||
| /// </returns> | |||||
| public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | ||||
| => ChannelHelper.GetWebhookAsync(this, Discord, id, options); | => ChannelHelper.GetWebhookAsync(this, Discord, id, options); | ||||
| /// <summary> | |||||
| /// Gets the webhooks for this text channel. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of webhooks. | |||||
| /// </returns> | |||||
| public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | ||||
| => ChannelHelper.GetWebhooksAsync(this, Discord, options); | => ChannelHelper.GetWebhooksAsync(this, Discord, options); | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.Audio; | |||||
| using Discord.Audio; | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -10,12 +10,18 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a WebSocket-based voice channel in a guild. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel | public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel | ||||
| { | { | ||||
| /// <inheritdoc /> | |||||
| public int Bitrate { get; private set; } | public int Bitrate { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public int? UserLimit { get; private set; } | public int? UserLimit { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public override IReadOnlyCollection<SocketGuildUser> Users | public override IReadOnlyCollection<SocketGuildUser> Users | ||||
| => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); | => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); | ||||
| @@ -37,14 +43,17 @@ namespace Discord.WebSocket | |||||
| UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; | UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) | public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) | ||||
| => ChannelHelper.ModifyAsync(this, Discord, func, options); | => ChannelHelper.ModifyAsync(this, Discord, func, options); | ||||
| /// <inheritdoc /> | |||||
| public async Task<IAudioClient> ConnectAsync(Action<IAudioClient> configAction = null) | public async Task<IAudioClient> ConnectAsync(Action<IAudioClient> configAction = null) | ||||
| { | { | ||||
| return await Guild.ConnectAudioAsync(Id, false, false, configAction).ConfigureAwait(false); | return await Guild.ConnectAudioAsync(Id, false, false, configAction).ConfigureAwait(false); | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public override SocketGuildUser GetUser(ulong id) | public override SocketGuildUser GetUser(ulong id) | ||||
| { | { | ||||
| var user = Guild.GetUser(id); | var user = Guild.GetUser(id); | ||||
| @@ -57,8 +66,10 @@ namespace Discord.WebSocket | |||||
| internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; | internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; | ||||
| //IGuildChannel | //IGuildChannel | ||||
| /// <inheritdoc /> | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | => Task.FromResult<IGuildUser>(GetUser(id)); | ||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | ||||
| } | } | ||||
| @@ -21,6 +21,10 @@ using VoiceStateModel = Discord.API.VoiceState; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a WebSocket-based guild object. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class SocketGuild : SocketEntity<ulong>, IGuild | public class SocketGuild : SocketEntity<ulong>, IGuild | ||||
| { | { | ||||
| private readonly SemaphoreSlim _audioLock; | private readonly SemaphoreSlim _audioLock; | ||||
| @@ -46,11 +50,13 @@ namespace Discord.WebSocket | |||||
| public MfaLevel MfaLevel { get; private set; } | public MfaLevel MfaLevel { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } | public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } | ||||
| /// <summary> Gets the number of members. </summary> | |||||
| /// <remark> | |||||
| /// The number of members is returned by Discord and is the most accurate. | |||||
| /// You may see discrepancy between the Users collection and this. | |||||
| /// </remark> | |||||
| /// <summary> | |||||
| /// Gets the number of members. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// The number of members is returned by Discord and is the most accurate. You may see discrepancy between | |||||
| /// the <see cref="Users"/> collection and this. | |||||
| /// </remarks> | |||||
| public int MemberCount { get; internal set; } | public int MemberCount { get; internal set; } | ||||
| /// <summary> Gets the number of members downloaded to the local guild cache. </summary> | /// <summary> Gets the number of members downloaded to the local guild cache. </summary> | ||||
| public int DownloadedMemberCount { get; private set; } | public int DownloadedMemberCount { get; private set; } | ||||
| @@ -84,11 +90,23 @@ namespace Discord.WebSocket | |||||
| public bool IsSynced => _syncPromise.Task.IsCompleted; | public bool IsSynced => _syncPromise.Task.IsCompleted; | ||||
| public Task SyncPromise => _syncPromise.Task; | public Task SyncPromise => _syncPromise.Task; | ||||
| public Task DownloaderPromise => _downloaderPromise.Task; | public Task DownloaderPromise => _downloaderPromise.Task; | ||||
| /// <summary> | |||||
| /// Returns the <see cref="IAudioClient" /> associated with this guild. | |||||
| /// </summary> | |||||
| public IAudioClient AudioClient => _audioClient; | public IAudioClient AudioClient => _audioClient; | ||||
| /// <summary> | |||||
| /// Returns the first viewable text channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This property does not guarantee the user can send message to it. | |||||
| /// </remarks> | |||||
| public SocketTextChannel DefaultChannel => TextChannels | public SocketTextChannel DefaultChannel => TextChannels | ||||
| .Where(c => CurrentUser.GetPermissions(c).ViewChannel) | .Where(c => CurrentUser.GetPermissions(c).ViewChannel) | ||||
| .OrderBy(c => c.Position) | .OrderBy(c => c.Position) | ||||
| .FirstOrDefault(); | .FirstOrDefault(); | ||||
| /// <summary> | |||||
| /// Returns the AFK voice channel, or <see langword="null" /> if none is set. | |||||
| /// </summary> | |||||
| public SocketVoiceChannel AFKChannel | public SocketVoiceChannel AFKChannel | ||||
| { | { | ||||
| get | get | ||||
| @@ -97,6 +115,9 @@ namespace Discord.WebSocket | |||||
| return id.HasValue ? GetVoiceChannel(id.Value) : null; | return id.HasValue ? GetVoiceChannel(id.Value) : null; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets the embed channel set in the widget settings of this guild, or <see langword="null"/> if none is set. | |||||
| /// </summary> | |||||
| public SocketGuildChannel EmbedChannel | public SocketGuildChannel EmbedChannel | ||||
| { | { | ||||
| get | get | ||||
| @@ -105,6 +126,9 @@ namespace Discord.WebSocket | |||||
| return id.HasValue ? GetChannel(id.Value) : null; | return id.HasValue ? GetChannel(id.Value) : null; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets the channel where randomized welcome messages are sent, or <see langword="null"/> if none is set. | |||||
| /// </summary> | |||||
| public SocketTextChannel SystemChannel | public SocketTextChannel SystemChannel | ||||
| { | { | ||||
| get | get | ||||
| @@ -113,14 +137,32 @@ namespace Discord.WebSocket | |||||
| return id.HasValue ? GetTextChannel(id.Value) : null; | return id.HasValue ? GetTextChannel(id.Value) : null; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Returns a collection of text channels present in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketTextChannel> TextChannels | public IReadOnlyCollection<SocketTextChannel> TextChannels | ||||
| => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); | => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); | ||||
| /// <summary> | |||||
| /// Returns a collection of voice channels present in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | ||||
| => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); | => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); | ||||
| /// <summary> | |||||
| /// Returns a collection of category channels present in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | ||||
| => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); | => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); | ||||
| /// <summary> | |||||
| /// Returns the current logged-in user. | |||||
| /// </summary> | |||||
| public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; | public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; | ||||
| /// <summary> | |||||
| /// Returns the @everyone role in this guild. | |||||
| /// </summary> | |||||
| public SocketRole EveryoneRole => GetRole(Id); | public SocketRole EveryoneRole => GetRole(Id); | ||||
| /// <summary> | |||||
| /// Returns a collection of channels present in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketGuildChannel> Channels | public IReadOnlyCollection<SocketGuildChannel> Channels | ||||
| { | { | ||||
| get | get | ||||
| @@ -130,9 +172,26 @@ namespace Discord.WebSocket | |||||
| return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); | return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets a collection of emotes created in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<GuildEmote> Emotes => _emotes; | public IReadOnlyCollection<GuildEmote> Emotes => _emotes; | ||||
| /// <summary> | |||||
| /// Gets a collection of features enabled in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<string> Features => _features; | public IReadOnlyCollection<string> Features => _features; | ||||
| /// <summary> | |||||
| /// Gets a collection of users in this guild. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This property may not always return all the members for large guilds (i.e. guilds containing 100+ users). | |||||
| /// You may need to enable <see cref="DiscordSocketConfig.AlwaysDownloadUsers"/> to fetch the full user list | |||||
| /// upon startup, or use <see cref="DownloadUsersAsync"/> to manually download the users. | |||||
| /// </remarks> | |||||
| public IReadOnlyCollection<SocketGuildUser> Users => _members.ToReadOnlyCollection(); | public IReadOnlyCollection<SocketGuildUser> Users => _members.ToReadOnlyCollection(); | ||||
| /// <summary> | |||||
| /// Gets a collection of roles in this guild. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection(); | public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection(); | ||||
| internal SocketGuild(DiscordSocketClient client, ulong id) | internal SocketGuild(DiscordSocketClient client, ulong id) | ||||
| @@ -315,7 +374,13 @@ namespace Discord.WebSocket | |||||
| => GuildHelper.LeaveAsync(this, Discord, options); | => GuildHelper.LeaveAsync(this, Discord, options); | ||||
| //Bans | //Bans | ||||
| /// <summary> Gets a collection of the banned users in this guild. </summary> | |||||
| /// <summary> | |||||
| /// Gets a collection of the banned users in this guild. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of bans. | |||||
| /// </returns> | |||||
| public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null) | ||||
| => GuildHelper.GetBansAsync(this, Discord, options); | => GuildHelper.GetBansAsync(this, Discord, options); | ||||
| @@ -334,6 +399,13 @@ namespace Discord.WebSocket | |||||
| => GuildHelper.RemoveBanAsync(this, Discord, userId, options); | => GuildHelper.RemoveBanAsync(this, Discord, userId, options); | ||||
| //Channels | //Channels | ||||
| /// <summary> | |||||
| /// Returns a guild channel with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The channel ID.</param> | |||||
| /// <returns> | |||||
| /// The guild channel associated with the ID. | |||||
| /// </returns> | |||||
| public SocketGuildChannel GetChannel(ulong id) | public SocketGuildChannel GetChannel(ulong id) | ||||
| { | { | ||||
| var channel = Discord.State.GetChannel(id) as SocketGuildChannel; | var channel = Discord.State.GetChannel(id) as SocketGuildChannel; | ||||
| @@ -341,14 +413,52 @@ namespace Discord.WebSocket | |||||
| return channel; | return channel; | ||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Returns a text channel with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The channel ID.</param> | |||||
| /// <returns> | |||||
| /// The text channel associated with the ID. | |||||
| /// </returns> | |||||
| public SocketTextChannel GetTextChannel(ulong id) | public SocketTextChannel GetTextChannel(ulong id) | ||||
| => GetChannel(id) as SocketTextChannel; | => GetChannel(id) as SocketTextChannel; | ||||
| /// <summary> | |||||
| /// Returns a voice channel with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The channel ID.</param> | |||||
| /// <returns> | |||||
| /// The voice channel associated with the ID. | |||||
| /// </returns> | |||||
| public SocketVoiceChannel GetVoiceChannel(ulong id) | public SocketVoiceChannel GetVoiceChannel(ulong id) | ||||
| => GetChannel(id) as SocketVoiceChannel; | => 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> | |||||
| /// <returns> | |||||
| /// The created text channel. | |||||
| /// </returns> | |||||
| public Task<RestTextChannel> CreateTextChannelAsync(string name, RequestOptions options = null) | public Task<RestTextChannel> CreateTextChannelAsync(string name, RequestOptions options = null) | ||||
| => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); | => 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> | |||||
| /// <returns> | |||||
| /// The created voice channel. | |||||
| /// </returns> | |||||
| public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null) | public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null) | ||||
| => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); | => 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> | |||||
| /// <returns> | |||||
| /// The created category channel. | |||||
| /// </returns> | |||||
| public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, RequestOptions options = null) | public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, RequestOptions options = null) | ||||
| => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); | => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); | ||||
| @@ -373,16 +483,43 @@ namespace Discord.WebSocket | |||||
| => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); | => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); | ||||
| //Invites | //Invites | ||||
| /// <summary> | |||||
| /// Returns a collection of invites associated with this channel. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of invites. | |||||
| /// </returns> | |||||
| public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | ||||
| => GuildHelper.GetInvitesAsync(this, Discord, options); | => GuildHelper.GetInvitesAsync(this, Discord, options); | ||||
| //Roles | //Roles | ||||
| /// <summary> | |||||
| /// Returns a role with the provided role ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The ID of the role.</param> | |||||
| /// <returns> | |||||
| /// The role associated with the ID. | |||||
| /// </returns> | |||||
| public SocketRole GetRole(ulong id) | public SocketRole GetRole(ulong id) | ||||
| { | { | ||||
| if (_roles.TryGetValue(id, out SocketRole value)) | if (_roles.TryGetValue(id, out SocketRole value)) | ||||
| return value; | return value; | ||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Creates a role. | |||||
| /// </summary> | |||||
| /// <param name="name">The name of the new role.</param> | |||||
| /// <param name="permissions"> | |||||
| /// The permissions that the new role possesses. Set to <see langword="null" /> to use the default permissions. | |||||
| /// </param> | |||||
| /// <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> | |||||
| /// <returns> | |||||
| /// The created role. | |||||
| /// </returns> | |||||
| public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | ||||
| bool isHoisted = false, RequestOptions options = null) | bool isHoisted = false, RequestOptions options = null) | ||||
| => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); | => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); | ||||
| @@ -400,12 +537,20 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| //Users | //Users | ||||
| /// <summary> | |||||
| /// Gets the user with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The ID of the user.</param> | |||||
| /// <returns> | |||||
| /// The user associated with the ID. | |||||
| /// </returns> | |||||
| public SocketGuildUser GetUser(ulong id) | public SocketGuildUser GetUser(ulong id) | ||||
| { | { | ||||
| if (_members.TryGetValue(id, out SocketGuildUser member)) | if (_members.TryGetValue(id, out SocketGuildUser member)) | ||||
| return member; | return member; | ||||
| return null; | return null; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) | public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) | ||||
| => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); | => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); | ||||
| @@ -459,6 +604,9 @@ namespace Discord.WebSocket | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Downloads the users of this guild to the WebSocket cache. | |||||
| /// </summary> | |||||
| public async Task DownloadUsersAsync() | public async Task DownloadUsersAsync() | ||||
| { | { | ||||
| await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); | await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); | ||||
| @@ -469,8 +617,23 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| //Webhooks | //Webhooks | ||||
| /// <summary> | |||||
| /// Returns the webhook with the provided ID. | |||||
| /// </summary> | |||||
| /// <param name="id">The ID of the webhook.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A webhook associated with the ID. | |||||
| /// </returns> | |||||
| public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | ||||
| => GuildHelper.GetWebhookAsync(this, Discord, id, options); | => GuildHelper.GetWebhookAsync(this, Discord, id, options); | ||||
| /// <summary> | |||||
| /// Gets a collection of webhooks that exist in the guild. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A collection of webhooks. | |||||
| /// </returns> | |||||
| public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | ||||
| => GuildHelper.GetWebhooksAsync(this, Discord, options); | => GuildHelper.GetWebhooksAsync(this, Discord, options); | ||||
| @@ -592,7 +755,7 @@ namespace Discord.WebSocket | |||||
| try | try | ||||
| { | { | ||||
| var timeoutTask = Task.Delay(15000); | var timeoutTask = Task.Delay(15000); | ||||
| if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask) | |||||
| if (await Task.WhenAny(promise.Task, timeoutTask).ConfigureAwait(false) == timeoutTask) | |||||
| throw new TimeoutException(); | throw new TimeoutException(); | ||||
| return await promise.Task.ConfigureAwait(false); | return await promise.Task.ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -663,6 +826,9 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets the name of the guild. | |||||
| /// </summary> | |||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
| internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; | internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; | ||||
| @@ -12,7 +12,9 @@ using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> The WebSocket variant of <see cref="IGuildUser"/>. Represents a Discord user that is in a guild. </summary> | |||||
| /// <summary> | |||||
| /// Represents a WebSocket guild user. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketGuildUser : SocketUser, IGuildUser | public class SocketGuildUser : SocketUser, IGuildUser | ||||
| { | { | ||||
| @@ -20,6 +22,9 @@ namespace Discord.WebSocket | |||||
| private ImmutableArray<ulong> _roleIds; | private ImmutableArray<ulong> _roleIds; | ||||
| internal override SocketGlobalUser GlobalUser { get; } | internal override SocketGlobalUser GlobalUser { get; } | ||||
| /// <summary> | |||||
| /// Gets the guild the user is in. | |||||
| /// </summary> | |||||
| public SocketGuild Guild { get; } | public SocketGuild Guild { get; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Nickname { get; private set; } | public string Nickname { get; private set; } | ||||
| @@ -50,17 +55,27 @@ namespace Discord.WebSocket | |||||
| public bool IsMuted => VoiceState?.IsMuted ?? false; | public bool IsMuted => VoiceState?.IsMuted ?? false; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | ||||
| /// <summary> | |||||
| /// Returns a collection of roles that the user possesses. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketRole> Roles | public IReadOnlyCollection<SocketRole> Roles | ||||
| => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); | => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); | ||||
| /// <summary> | |||||
| /// Returns the voice channel the user is in, or <see langword="null" /> if none. | |||||
| /// </summary> | |||||
| public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; | public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; | ||||
| public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); | public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); | ||||
| public AudioInStream AudioStream => Guild.GetAudioStream(Id); | public AudioInStream AudioStream => Guild.GetAudioStream(Id); | ||||
| /// <summary> The position of the user within the role hierarchy. </summary> | |||||
| /// <remarks> The returned value equal to the position of the highest role the user has, | |||||
| /// or <see cref="int.MaxValue"/> if user is the server owner. </remarks> | |||||
| /// <summary> | |||||
| /// Returns the position of the user within the role hierarchy. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// The returned value equal to the position of the highest role the user has, or | |||||
| /// <see cref="int.MaxValue"/> if user is the server owner. | |||||
| /// </remarks> | |||||
| public int Hierarchy | public int Hierarchy | ||||
| { | { | ||||
| get | get | ||||