| @@ -37,6 +37,7 @@ | |||
| "default", | |||
| "_template/light-dark-theme" | |||
| ], | |||
| "postProcessors": [ "ExtractSearchIndex" ], | |||
| "overwrite": "_overwrites/**/**.md", | |||
| "globalMetadata": { | |||
| "_appTitle": "Discord.Net Documentation", | |||
| @@ -19,6 +19,7 @@ namespace Discord.Commands | |||
| /// <inheritdoc/> | |||
| /// <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) | |||
| { | |||
| if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | |||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||
| /// <remarks> | |||
| /// <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 | |||
| /// require *all* preconditions to pass, just like normal (A && B). | |||
| /// require *all* preconditions to pass, just like normal (A && B). | |||
| /// </remarks> | |||
| 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> | |||
| 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) | |||
| { | |||
| Client = client; | |||
| @@ -13,11 +13,14 @@ namespace Discord.Commands | |||
| { | |||
| 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); } } | |||
| internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||
| /// <summary> | |||
| /// Fired when a command is successfully executed without any runtime error. | |||
| /// Occurs when a command is successfully executed without any runtime error. | |||
| /// </summary> | |||
| 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>>(); | |||
| @@ -51,7 +54,16 @@ namespace Discord.Commands | |||
| /// </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); | |||
| /// <summary> | |||
| /// Initializes a new <see cref="CommandService"/> class. | |||
| /// </summary> | |||
| 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) | |||
| { | |||
| _caseSensitive = config.CaseSensitiveCommands; | |||
| @@ -121,6 +133,7 @@ namespace Discord.Commands | |||
| /// A built module. | |||
| /// </returns> | |||
| public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services); | |||
| /// <summary> | |||
| /// Adds a command module from a <see cref="Type" /> . | |||
| /// </summary> | |||
| @@ -132,6 +145,8 @@ namespace Discord.Commands | |||
| /// <returns> | |||
| /// A built module. | |||
| /// </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) | |||
| { | |||
| services = services ?? EmptyServiceProvider.Instance; | |||
| @@ -209,7 +224,7 @@ namespace Discord.Commands | |||
| /// </summary> | |||
| /// <param name="module">The <see cref="ModuleInfo" /> to be removed from the service.</param> | |||
| /// <returns> | |||
| /// Returns whether the <paramref name="module"/> is successfully removed. | |||
| /// Returns whether the module is successfully removed. | |||
| /// </returns> | |||
| public async Task<bool> RemoveModuleAsync(ModuleInfo module) | |||
| { | |||
| @@ -223,7 +238,21 @@ namespace Discord.Commands | |||
| _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)); | |||
| /// <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) | |||
| { | |||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
| @@ -2,23 +2,40 @@ using System; | |||
| namespace Discord.Commands | |||
| { | |||
| /// <summary> | |||
| /// Represents a configuration class for <see cref="CommandService" />. | |||
| /// </summary> | |||
| 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; | |||
| /// <summary> | |||
| /// Gets or sets the <see cref="char"/> that separates an argument with another. | |||
| /// </summary> | |||
| 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; | |||
| /// <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; | |||
| /// <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; | |||
| /// <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; | |||
| ///// <summary> Gets or sets the <see cref="IServiceProvider"/> to use. </summary> | |||
| @@ -9,32 +9,45 @@ namespace Discord.Commands | |||
| public abstract class ModuleBase<T> : IModuleBase | |||
| 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; } | |||
| /// <summary> | |||
| /// Sends a message to the source channel | |||
| /// Sends a message to the source channel. | |||
| /// </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) | |||
| { | |||
| return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| /// The method to execute before executing the command. | |||
| /// The method to execute before executing the command. | |||
| /// </summary> | |||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||
| protected virtual void BeforeExecute(CommandInfo command) | |||
| { | |||
| } | |||
| /// <summary> | |||
| /// The method to execute after executing the command. | |||
| /// The method to execute after executing the command. | |||
| /// </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) | |||
| { | |||
| } | |||
| /// <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) | |||
| { | |||
| } | |||
| @@ -1,5 +1,8 @@ | |||
| namespace Discord.Commands | |||
| { | |||
| /// <summary> | |||
| /// Specifies the behavior when multiple matches are found during the command parsing stage. | |||
| /// </summary> | |||
| public enum MultiMatchHandling | |||
| { | |||
| /// <summary> Indicates that when multiple results are found, an exception should be thrown. </summary> | |||
| @@ -1,5 +1,10 @@ | |||
| namespace Discord.Commands | |||
| { | |||
| /// <summary> | |||
| /// Specifies the behavior of the command execution workflow. | |||
| /// </summary> | |||
| /// <seealso cref="CommandServiceConfig"/> | |||
| /// <seealso cref="CommandAttribute"/> | |||
| public enum RunMode | |||
| { | |||
| /// <summary> | |||
| @@ -20,16 +20,13 @@ namespace Discord | |||
| /// 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} | |||
| /// </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; } | |||
| /// <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> | |||
| public Optional<int> Position { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets the category for this channel. | |||
| /// Gets or sets the category ID for this channel. | |||
| /// </summary> | |||
| public Optional<ulong?> CategoryId { get; set; } | |||
| } | |||
| @@ -4,7 +4,7 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a generic Discord channel. | |||
| /// Represents a generic channel. | |||
| /// </summary> | |||
| public interface IChannel : ISnowflakeEntity | |||
| { | |||
| @@ -3,7 +3,7 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a generic DM channel. | |||
| /// Represents a generic direct-message channel. | |||
| /// </summary> | |||
| public interface IDMChannel : IMessageChannel, IPrivateChannel | |||
| { | |||
| @@ -15,6 +15,7 @@ namespace Discord | |||
| /// <summary> | |||
| /// Closes this private channel, removing it from your channel list. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task CloseAsync(RequestOptions options = null); | |||
| } | |||
| } | |||
| @@ -3,13 +3,14 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a private generic group channel. | |||
| /// Represents a generic private group channel. | |||
| /// </summary> | |||
| public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel | |||
| { | |||
| /// <summary> | |||
| /// Leaves this group. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task LeaveAsync(RequestOptions options = null); | |||
| } | |||
| } | |||
| @@ -5,34 +5,52 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a guild channel (text, voice, category). | |||
| /// Represents a generic guild channel. | |||
| /// </summary> | |||
| /// <seealso cref="ITextChannel"/> | |||
| /// <seealso cref="IVoiceChannel"/> | |||
| /// <seealso cref="ICategoryChannel"/> | |||
| public interface IGuildChannel : IChannel, IDeletable | |||
| { | |||
| /// <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> | |||
| /// <returns> | |||
| /// The position of this channel in the guild's channel list, relative to others of the same type. | |||
| /// </returns> | |||
| int Position { get; } | |||
| /// <summary> | |||
| /// Gets the parent ID (category) of this channel in the guild's channel list. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// The parent category ID associated with this channel, or <see langword="null"/> if none is set. | |||
| /// </returns> | |||
| ulong? CategoryId { get; } | |||
| /// <summary> | |||
| /// Gets the parent channel (category) of this channel. | |||
| /// </summary> | |||
| Task<ICategoryChannel> GetCategoryAsync(); | |||
| /// <summary> | |||
| /// Gets the guild this channel is a member of. | |||
| /// Gets the guild associated with this channel. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// The guild that this channel belongs to. | |||
| /// </returns> | |||
| IGuild Guild { get; } | |||
| /// <summary> | |||
| /// Gets the id of the guild this channel is a member of. | |||
| /// Gets the guild ID associated with this channel. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// The guild ID that this channel belongs to. | |||
| /// </returns> | |||
| ulong GuildId { get; } | |||
| /// <summary> | |||
| /// Gets a collection of permission overwrites for this channel. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A collection of overwrites associated with this channel. | |||
| /// </returns> | |||
| IReadOnlyCollection<Overwrite> PermissionOverwrites { get; } | |||
| /// <summary> | |||
| @@ -50,49 +68,77 @@ namespace Discord | |||
| /// <param name="isUnique"> | |||
| /// If <see langword="true"/>, don't try to reuse a similar invite (useful for creating many unique one time use invites). | |||
| /// </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); | |||
| /// <summary> | |||
| /// Returns a collection of all invites to this channel. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null); | |||
| /// <summary> | |||
| /// Modifies this guild channel. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Gets the permission overwrite for a specific role, or <see langword="null"/> if one does not exist. | |||
| /// </summary> | |||
| /// <param name="role">The role to get the overwrite from.</param> | |||
| OverwritePermissions? GetPermissionOverwrite(IRole role); | |||
| /// <summary> | |||
| /// Gets the permission overwrite for a specific user, or <see langword="null"/> if one does not exist. | |||
| /// </summary> | |||
| /// <param name="user">The user to get the overwrite from.</param> | |||
| OverwritePermissions? GetPermissionOverwrite(IUser user); | |||
| /// <summary> | |||
| /// Removes the permission overwrite for the given role, if one exists. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Removes the permission overwrite for the given user, if one exists. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Adds or updates the permission overwrite for the given role. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Adds or updates the permission overwrite for the given user. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Gets a collection of all users in this channel. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Gets a user in this channel with the provided ID. | |||
| /// </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); | |||
| } | |||
| } | |||
| @@ -13,50 +13,115 @@ namespace Discord | |||
| /// <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> | |||
| Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||
| #if FILESYSTEM | |||
| /// <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> | |||
| Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||
| #endif | |||
| /// <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> | |||
| Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a message from this message channel with the given id, or <see langword="null"/> if not found. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Gets the last N messages from this message channel. | |||
| /// </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, | |||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of messages in this channel. | |||
| /// </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, | |||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of messages in this channel. | |||
| /// </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, | |||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <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> | |||
| Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | |||
| /// <summary> | |||
| /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task TriggerTypingAsync(RequestOptions options = null); | |||
| /// <summary> | |||
| /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned | |||
| /// object is disposed. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| IDisposable EnterTypingState(RequestOptions options = null); | |||
| } | |||
| } | |||
| @@ -8,8 +8,11 @@ namespace Discord | |||
| public interface IPrivateChannel : IChannel | |||
| { | |||
| /// <summary> | |||
| /// Users that can access this channel. | |||
| /// Gets the users that can access this channel. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A collection of users that can access this channel. | |||
| /// </returns> | |||
| IReadOnlyCollection<IUser> Recipients { get; } | |||
| } | |||
| } | |||
| @@ -11,40 +11,67 @@ namespace Discord | |||
| public interface ITextChannel : IMessageChannel, IMentionable, IGuildChannel | |||
| { | |||
| /// <summary> | |||
| /// Gets whether the channel is NSFW. | |||
| /// Determines whether the channel is NSFW. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// <see langword="true"/> if the channel has the NSFW flag enabled; otherwise, <see langword="false"/>. | |||
| /// </returns> | |||
| bool IsNsfw { get; } | |||
| /// <summary> | |||
| /// Gets the current topic for this text channel. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// The topic set in the channel, or <see langword="null"/> if none is set. | |||
| /// </returns> | |||
| string Topic { get; } | |||
| /// <summary> | |||
| /// Bulk deletes multiple messages. | |||
| /// Bulk-deletes multiple messages. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Bulk deletes multiple messages. | |||
| /// Bulk-deletes multiple messages. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Modifies this text channel. | |||
| /// </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); | |||
| /// <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> | |||
| Task<IWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null); | |||
| /// <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> | |||
| /// <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); | |||
| /// <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> | |||
| Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | |||
| } | |||
| } | |||
| @@ -21,6 +21,8 @@ namespace Discord | |||
| /// <summary> | |||
| /// Modifies this voice channel. | |||
| /// </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); | |||
| } | |||
| } | |||
| @@ -6,10 +6,8 @@ namespace Discord | |||
| public class Emoji : IEmote | |||
| { | |||
| // TODO: need to constrain this to Unicode-only emojis somehow | |||
| /// <summary> | |||
| /// Gets the Unicode representation of this emote. | |||
| /// </summary> | |||
| /// <inheritdoc /> | |||
| public string Name { get; } | |||
| /// <summary> | |||
| /// Gets the Unicode representation of this emote. | |||
| @@ -28,7 +26,7 @@ namespace Discord | |||
| /// <summary> | |||
| /// Determines whether the specified emoji is equal to the current emoji. | |||
| /// </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) | |||
| { | |||
| if (other == null) return false; | |||
| @@ -1,4 +1,5 @@ | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Globalization; | |||
| namespace Discord | |||
| @@ -6,15 +7,12 @@ namespace Discord | |||
| /// <summary> | |||
| /// A custom image-based emote. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class Emote : IEmote, ISnowflakeEntity | |||
| { | |||
| /// <summary> | |||
| /// Gets the display name (tooltip) of this emote. | |||
| /// </summary> | |||
| /// <inheritdoc /> | |||
| public string Name { get; } | |||
| /// <summary> | |||
| /// Gets the ID of this emote. | |||
| /// </summary> | |||
| /// <inheritdoc /> | |||
| public ulong Id { get; } | |||
| /// <summary> | |||
| /// Gets whether this emote is animated. | |||
| @@ -36,6 +34,10 @@ namespace Discord | |||
| 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) | |||
| { | |||
| if (other == null) return false; | |||
| @@ -47,6 +49,7 @@ namespace Discord | |||
| return string.Equals(Name, otherEmote.Name) && Id == otherEmote.Id; | |||
| } | |||
| /// <inheritdoc /> | |||
| public override int GetHashCode() | |||
| { | |||
| unchecked | |||
| @@ -57,7 +60,8 @@ namespace Discord | |||
| /// <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> | |||
| /// <returns>An emote</returns> | |||
| /// <returns>An emote.</returns> | |||
| /// <exception cref="ArgumentException">Invalid emote format.</exception> | |||
| public static Emote Parse(string text) | |||
| { | |||
| if (TryParse(text, out Emote result)) | |||
| @@ -65,6 +69,9 @@ namespace Discord | |||
| 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) | |||
| { | |||
| result = null; | |||
| @@ -89,6 +96,9 @@ namespace Discord | |||
| } | |||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||
| /// <summary> | |||
| /// Returns the raw representation of the emote. | |||
| /// </summary> | |||
| public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; | |||
| } | |||
| } | |||
| @@ -31,7 +31,7 @@ namespace Discord | |||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||
| /// <summary> | |||
| /// Gets the raw representation of the emoji. | |||
| /// Gets the raw representation of the emote. | |||
| /// </summary> | |||
| public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; | |||
| } | |||
| @@ -8,9 +8,8 @@ namespace Discord | |||
| /// await Context.Guild.ModifyAsync(async x => | |||
| /// { | |||
| /// x.Name = "aaaaaah"; | |||
| /// x.RegionId = (await Context.Client.GetOptimalVoiceRegionAsync()).Id; | |||
| /// }); | |||
| /// </code> | |||
| /// </code> | |||
| /// </example> | |||
| /// <see cref="T:Discord.IGuild" /> | |||
| public class GuildProperties | |||
| @@ -61,11 +60,11 @@ namespace Discord | |||
| /// </summary> | |||
| public Optional<ulong?> AfkChannelId { get; set; } | |||
| /// <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> | |||
| public Optional<ITextChannel> SystemChannel { get; set; } | |||
| /// <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> | |||
| public Optional<ulong?> SystemChannelId { get; set; } | |||
| /// <summary> | |||
| @@ -20,8 +20,11 @@ namespace Discord | |||
| /// </summary> | |||
| int AFKTimeout { get; } | |||
| /// <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> | |||
| /// <returns> | |||
| /// Returns <see langword="true"/> if this guild can be embedded via widgets. | |||
| /// </returns> | |||
| bool IsEmbeddable { get; } | |||
| /// <summary> | |||
| /// Gets the default message notifications for users who haven't explicitly set their notification settings. | |||
| @@ -37,29 +40,32 @@ namespace Discord | |||
| /// </summary> | |||
| VerificationLevel VerificationLevel { get; } | |||
| /// <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> | |||
| string IconId { get; } | |||
| /// <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> | |||
| string IconUrl { get; } | |||
| /// <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> | |||
| string SplashId { get; } | |||
| /// <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> | |||
| string SplashUrl { get; } | |||
| /// <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 | |||
| /// to the WebSocket client. | |||
| /// </summary> | |||
| /// </returns> | |||
| bool Available { get; } | |||
| /// <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> | |||
| ulong? AFKChannelId { get; } | |||
| /// <summary> | |||
| @@ -67,11 +73,11 @@ namespace Discord | |||
| /// </summary> | |||
| ulong DefaultChannelId { get; } | |||
| /// <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> | |||
| ulong? EmbedChannelId { get; } | |||
| /// <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> | |||
| ulong? SystemChannelId { get; } | |||
| /// <summary> | |||
| @@ -106,57 +112,62 @@ namespace Discord | |||
| /// <summary> | |||
| /// Modifies this guild. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Modifies this guild's embed channel. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Bulk modifies the order of channels in this guild. | |||
| /// </summary> | |||
| /// <param name="args">The properties to modify the channel positions with.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Bulk modifies the order of roles in this guild. | |||
| /// </summary> | |||
| /// <param name="args">The properties to modify the role positions with.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null); | |||
| /// <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> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task LeaveAsync(RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all users banned on this guild. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); | |||
| /// <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> | |||
| /// <param name="user"> | |||
| /// The user to ban. | |||
| /// </param> | |||
| /// <param name="user">The user to ban.</param> | |||
| /// <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 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); | |||
| /// <summary> | |||
| /// Bans the provided user ID from this guild and optionally prunes their recent messages. | |||
| /// </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"> | |||
| /// 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 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); | |||
| /// <summary> | |||
| /// Unbans the provided <paramref name="user"/> if they are currently banned. | |||
| /// Unbans the provided user if they are currently banned. | |||
| /// </summary> | |||
| Task RemoveBanAsync(IUser user, RequestOptions options = null); | |||
| /// <summary> | |||
| @@ -167,66 +178,114 @@ namespace Discord | |||
| /// <summary> | |||
| /// Gets a collection of all channels in this guild. | |||
| /// </summary> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets the channel in this guild with the provided ID, or <see langword="null"/> if not found. | |||
| /// Gets the channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
| /// </summary> | |||
| /// <param name="id">The channel ID.</param> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all text channels in this guild. | |||
| /// </summary> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a text channel in this guild with the provided ID, or <see langword="null"/> if not found. | |||
| /// Gets a text channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
| /// </summary> | |||
| /// <param name="id">The text channel ID.</param> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all voice channels in this guild. | |||
| /// </summary> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all category channels in this guild. | |||
| /// </summary> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets the voice channel in this guild with the provided ID, or <see langword="null"/> if not found. | |||
| /// Gets the voice channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
| /// </summary> | |||
| /// <param name="id">The text channel ID.</param> | |||
| /// <param name="mode"> | |||
| /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. | |||
| /// </param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets the voice AFK channel in this guild with the provided ID, or <see langword="null"/> if not found. | |||
| /// Gets the voice AFK channel in this guild with the provided ID, or <see langword="null" /> if not found. | |||
| /// </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); | |||
| /// <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. | |||
| /// </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); | |||
| /// <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. | |||
| /// </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); | |||
| /// <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> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Creates a new text channel. | |||
| /// </summary> | |||
| /// <param name="name">The new name for the text channel.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Creates a new voice channel. | |||
| /// </summary> | |||
| /// <param name="name">The new name for the voice channel.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Creates a new channel category. | |||
| /// </summary> | |||
| /// <param name="name">The new name for the category.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<ICategoryChannel> CreateCategoryAsync(string name, 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="color">The color of the role.</param> | |||
| /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all users in this guild. | |||
| /// </summary> | |||
| 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> | |||
| /// Gets the user in this guild with the provided ID, or <see langword="null"/> if not found. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Gets the current user for this guild. | |||
| /// </summary> | |||
| /// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets the owner of this guild. | |||
| /// </summary> | |||
| /// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IGuildUser> GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Downloads all users for this guild if the current list is incomplete. | |||
| @@ -274,11 +342,12 @@ namespace Discord | |||
| Task DownloadUsersAsync(); | |||
| /// <summary> | |||
| /// 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> | |||
| /// <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="options">The options to be used when sending the request.</param> | |||
| /// <returns> | |||
| /// The number of users removed from this guild. | |||
| /// </returns> | |||
| @@ -288,32 +357,41 @@ namespace Discord | |||
| /// Gets the webhook in this guild with the provided ID, or <see langword="null"/> if not found. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Gets a collection of all webhooks from this guild. | |||
| /// Gets a collection of all webhook from this guild. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a specific emote from this guild. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Creates a new emote in this guild. | |||
| /// Creates a new <see cref="GuildEmote"/> in this guild. | |||
| /// </summary> | |||
| /// <param name="name">The name of the guild 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="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); | |||
| /// <summary> | |||
| /// Modifies an existing <paramref name="emote"/> in this guild. | |||
| /// Modifies an existing <see cref="GuildEmote"/> in this guild. | |||
| /// </summary> | |||
| /// <param name="emote">The emote to be modified.</param> | |||
| /// <param name="func">The properties to modify the emote with.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Deletes an existing <paramref name="emote"/> from this guild. | |||
| /// Deletes an existing <see cref="GuildEmote"/> from this guild. | |||
| /// </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); | |||
| } | |||
| } | |||
| @@ -3,13 +3,14 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents whether the object is deletable or not. | |||
| /// Determines whether the object is deletable or not. | |||
| /// </summary> | |||
| public interface IDeletable | |||
| { | |||
| /// <summary> | |||
| /// Deletes this object and all its children. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| Task DeleteAsync(RequestOptions options = null); | |||
| } | |||
| } | |||
| @@ -1,13 +1,16 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents whether the object is mentionable or not. | |||
| /// Determines whether the object is mentionable or not. | |||
| /// </summary> | |||
| public interface IMentionable | |||
| { | |||
| /// <summary> | |||
| /// Returns a special string used to mention this object. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string that is recognized by Discord as a mention (e.g. <@168693960628371456>). | |||
| /// </returns> | |||
| string Mention { get; } | |||
| } | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| using System; | |||
| using System.IO; | |||
| namespace Discord | |||
| { | |||
| @@ -11,7 +12,7 @@ namespace Discord | |||
| /// </summary> | |||
| public Stream Stream { get; } | |||
| /// <summary> | |||
| /// Create the image with a <see cref="System.IO.Stream" /> . | |||
| /// Create the image with a <see cref="System.IO.Stream" />. | |||
| /// </summary> | |||
| /// <param name="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. | |||
| /// </summary> | |||
| /// <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> | |||
| /// <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) | |||
| { | |||
| Stream = File.OpenRead(path); | |||
| @@ -41,6 +41,9 @@ namespace Discord | |||
| } | |||
| /// <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 | |||
| { | |||
| get => _title; | |||
| @@ -50,7 +53,10 @@ namespace Discord | |||
| _title = value; | |||
| } | |||
| } | |||
| /// <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 | |||
| { | |||
| get => _description; | |||
| @@ -62,6 +68,8 @@ namespace Discord | |||
| } | |||
| /// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary> | |||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||
| /// <returns> The URL of the embed.</returns> | |||
| public string Url | |||
| { | |||
| get => _url; | |||
| @@ -72,6 +80,8 @@ namespace Discord | |||
| } | |||
| } | |||
| /// <summary> Gets or sets the thumbnail URL of an <see cref="Embed"/>. </summary> | |||
| /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||
| /// <returns> The thumbnail URL of the embed.</returns> | |||
| public string ThumbnailUrl | |||
| { | |||
| get => _thumbnail?.Url; | |||
| @@ -82,6 +92,8 @@ namespace Discord | |||
| } | |||
| } | |||
| /// <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 | |||
| { | |||
| get => _image?.Url; | |||
| @@ -91,7 +103,13 @@ namespace Discord | |||
| _image = new EmbedImage(value, null, null, null); | |||
| } | |||
| } | |||
| /// <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 | |||
| { | |||
| 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; } | |||
| /// <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; } | |||
| /// <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; } | |||
| /// <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; } | |||
| /// <summary> | |||
| /// Gets the total length of all embed properties. | |||
| /// </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 | |||
| { | |||
| get | |||
| @@ -130,9 +172,12 @@ namespace Discord | |||
| } | |||
| /// <summary> | |||
| /// Sets the title of an <see cref="Embed" />. | |||
| /// Sets the title of an <see cref="Embed" /> . | |||
| /// </summary> | |||
| /// <param name="title">The title to be set.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithTitle(string title) | |||
| { | |||
| Title = title; | |||
| @@ -142,6 +187,9 @@ namespace Discord | |||
| /// Sets the description of an <see cref="Embed"/>. | |||
| /// </summary> | |||
| /// <param name="description"> The description to be set. </param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithDescription(string description) | |||
| { | |||
| Description = description; | |||
| @@ -151,6 +199,9 @@ namespace Discord | |||
| /// Sets the URL of an <see cref="Embed"/>. | |||
| /// </summary> | |||
| /// <param name="url"> The URL to be set. </param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithUrl(string url) | |||
| { | |||
| Url = url; | |||
| @@ -160,15 +211,21 @@ namespace Discord | |||
| /// Sets the thumbnail URL of an <see cref="Embed"/>. | |||
| /// </summary> | |||
| /// <param name="thumbnailUrl"> The thumbnail URL to be set. </param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) | |||
| { | |||
| ThumbnailUrl = thumbnailUrl; | |||
| return this; | |||
| } | |||
| /// <summary> | |||
| /// Sets the image URL of an <see cref="Embed" /> . | |||
| /// Sets the image URL of an <see cref="Embed" />. | |||
| /// </summary> | |||
| /// <param name="imageUrl">The image URL to be set.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithImageUrl(string imageUrl) | |||
| { | |||
| ImageUrl = imageUrl; | |||
| @@ -177,24 +234,33 @@ namespace Discord | |||
| /// <summary> | |||
| /// Sets the timestamp of an <see cref="Embed" /> to the current time. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithCurrentTimestamp() | |||
| { | |||
| Timestamp = DateTimeOffset.UtcNow; | |||
| return this; | |||
| } | |||
| /// <summary> | |||
| /// Sets the timestamp of an <see cref="Embed" /> . | |||
| /// Sets the timestamp of an <see cref="Embed" />. | |||
| /// </summary> | |||
| /// <param name="dateTimeOffset">The timestamp to be set.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) | |||
| { | |||
| Timestamp = dateTimeOffset; | |||
| return this; | |||
| } | |||
| /// <summary> | |||
| /// Sets the sidebar color of an <see cref="Embed" /> . | |||
| /// Sets the sidebar color of an <see cref="Embed" />. | |||
| /// </summary> | |||
| /// <param name="color">The color to be set.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithColor(Color color) | |||
| { | |||
| Color = color; | |||
| @@ -202,9 +268,12 @@ namespace Discord | |||
| } | |||
| /// <summary> | |||
| /// Sets the <see cref="EmbedAuthorBuilder" /> of an <see cref="Embed" /> . | |||
| /// Sets the <see cref="EmbedAuthorBuilder" /> of an <see cref="Embed" />. | |||
| /// </summary> | |||
| /// <param name="author">The author builder class containing the author field properties.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) | |||
| { | |||
| Author = author; | |||
| @@ -214,6 +283,9 @@ namespace Discord | |||
| /// Sets the author field of an <see cref="Embed" /> with the provided properties. | |||
| /// </summary> | |||
| /// <param name="action">The <see langword="delegate"/> containing the author field properties.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithAuthor(Action<EmbedAuthorBuilder> action) | |||
| { | |||
| var author = new EmbedAuthorBuilder(); | |||
| @@ -227,6 +299,9 @@ namespace Discord | |||
| /// <param name="name">The title 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> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) | |||
| { | |||
| var author = new EmbedAuthorBuilder | |||
| @@ -239,9 +314,12 @@ namespace Discord | |||
| return this; | |||
| } | |||
| /// <summary> | |||
| /// Sets the <see cref="EmbedFooterBuilder" /> of an <see cref="Embed" /> . | |||
| /// Sets the <see cref="EmbedFooterBuilder" /> of an <see cref="Embed" />. | |||
| /// </summary> | |||
| /// <param name="footer">The footer builder class containing the footer field properties.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithFooter(EmbedFooterBuilder footer) | |||
| { | |||
| Footer = footer; | |||
| @@ -251,6 +329,9 @@ namespace Discord | |||
| /// Sets the footer field of an <see cref="Embed" /> with the provided properties. | |||
| /// </summary> | |||
| /// <param name="action">The <see langword="delegate"/> containing the footer field properties.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder WithFooter(Action<EmbedFooterBuilder> action) | |||
| { | |||
| var footer = new EmbedFooterBuilder(); | |||
| @@ -263,6 +344,9 @@ namespace Discord | |||
| /// </summary> | |||
| /// <param name="text">The title 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) | |||
| { | |||
| var footer = new EmbedFooterBuilder | |||
| @@ -280,6 +364,9 @@ namespace Discord | |||
| /// <param name="name">The title 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> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder AddField(string name, object value, bool inline = false) | |||
| { | |||
| var field = new EmbedFieldBuilder() | |||
| @@ -289,11 +376,16 @@ namespace Discord | |||
| AddField(field); | |||
| return this; | |||
| } | |||
| /// <summary> | |||
| /// Adds a field with the provided <see cref="EmbedFieldBuilder" /> to an | |||
| /// <see cref="Embed" />. | |||
| /// </summary> | |||
| /// <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) | |||
| { | |||
| if (Fields.Count >= MaxFieldCount) | |||
| @@ -308,6 +400,9 @@ namespace Discord | |||
| /// Adds an <see cref="Embed" /> field with the provided properties. | |||
| /// </summary> | |||
| /// <param name="action">The <see langword="delegate"/> containing the field properties.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedBuilder AddField(Action<EmbedFieldBuilder> action) | |||
| { | |||
| var field = new EmbedFieldBuilder(); | |||
| @@ -317,11 +412,12 @@ namespace Discord | |||
| } | |||
| /// <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> | |||
| /// <returns> | |||
| /// The built embed object. | |||
| /// </returns> | |||
| /// <exception cref="InvalidOperationException">Total embed length exceeds the maximum allowed by Discord.</exception> | |||
| public Embed Build() | |||
| { | |||
| if (Length > MaxEmbedLength) | |||
| @@ -335,6 +431,9 @@ namespace Discord | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Represents a builder class for an embed field. | |||
| /// </summary> | |||
| public class EmbedFieldBuilder | |||
| { | |||
| private string _name; | |||
| @@ -352,6 +451,14 @@ namespace Discord | |||
| /// <summary> | |||
| /// Gets or sets the field name. | |||
| /// </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 | |||
| { | |||
| get => _name; | |||
| @@ -366,19 +473,27 @@ namespace Discord | |||
| /// <summary> | |||
| /// Gets or sets the field value. | |||
| /// </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 | |||
| { | |||
| get => _value; | |||
| set | |||
| { | |||
| 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)); | |||
| _value = stringValue; | |||
| } | |||
| } | |||
| /// <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> | |||
| public bool IsInline { get; set; } | |||
| @@ -386,6 +501,9 @@ namespace Discord | |||
| /// Sets the field name. | |||
| /// </summary> | |||
| /// <param name="name">The name to set the field name to.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedFieldBuilder WithName(string name) | |||
| { | |||
| Name = name; | |||
| @@ -395,14 +513,20 @@ namespace Discord | |||
| /// Sets the field value. | |||
| /// </summary> | |||
| /// <param name="value">The value to set the field value to.</param> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedFieldBuilder WithValue(object value) | |||
| { | |||
| Value = value; | |||
| return this; | |||
| } | |||
| /// <summary> | |||
| /// Sets whether the field should be in-line with each other. | |||
| /// Determines whether the field should be in-line with each other. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// The current builder. | |||
| /// </returns> | |||
| public EmbedFieldBuilder WithIsInline(bool isInline) | |||
| { | |||
| IsInline = isInline; | |||
| @@ -410,19 +534,42 @@ namespace Discord | |||
| } | |||
| /// <summary> | |||
| /// Builds the field builder into a <see cref="EmbedField"/> class. | |||
| /// Builds the field builder into a <see cref="EmbedField" /> class. | |||
| /// </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() | |||
| => new EmbedField(Name, Value.ToString(), IsInline); | |||
| } | |||
| /// <summary> | |||
| /// Represents a builder class for a author field. | |||
| /// </summary> | |||
| public class EmbedAuthorBuilder | |||
| { | |||
| private string _name; | |||
| private string _url; | |||
| private string _iconUrl; | |||
| /// <summary> | |||
| /// Gets the maximum author name length allowed by Discord. | |||
| /// </summary> | |||
| 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 | |||
| { | |||
| get => _name; | |||
| @@ -432,6 +579,13 @@ namespace Discord | |||
| _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 | |||
| { | |||
| get => _url; | |||
| @@ -441,6 +595,13 @@ namespace Discord | |||
| _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 | |||
| { | |||
| 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) | |||
| { | |||
| Name = name; | |||
| 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) | |||
| { | |||
| Url = url; | |||
| 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) | |||
| { | |||
| IconUrl = iconUrl; | |||
| 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() | |||
| => new EmbedAuthor(Name, Url, IconUrl, null); | |||
| } | |||
| /// <summary> | |||
| /// Represents a builder class for an embed footer. | |||
| /// </summary> | |||
| public class EmbedFooterBuilder | |||
| { | |||
| private string _text; | |||
| private string _iconUrl; | |||
| /// <summary> | |||
| /// Gets the maximum footer length allowed by Discord. | |||
| /// </summary> | |||
| 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 | |||
| { | |||
| get => _text; | |||
| @@ -487,6 +697,13 @@ namespace Discord | |||
| _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 | |||
| { | |||
| 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) | |||
| { | |||
| Text = text; | |||
| 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) | |||
| { | |||
| IconUrl = iconUrl; | |||
| 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() | |||
| => new EmbedFooter(Text, IconUrl, null); | |||
| } | |||
| @@ -17,7 +17,7 @@ namespace Discord | |||
| /// </summary> | |||
| public string Value { get; internal set; } | |||
| /// <summary> | |||
| /// Gets whether the field should be in-line with each other. | |||
| /// Determines whether the field should be in-line with each other. | |||
| /// </summary> | |||
| public bool Inline { get; internal set; } | |||
| @@ -83,12 +83,14 @@ namespace Discord | |||
| ((uint)g << 8) | | |||
| (uint)b; | |||
| } | |||
| /// <summary> | |||
| /// Initializes a <see cref="Color"/> struct with the given RGB value. | |||
| /// </summary> | |||
| /// <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="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) | |||
| { | |||
| 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="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> | |||
| /// <exception cref="ArgumentOutOfRangeException">The argument value is not between 0 to 1.</exception> | |||
| public Color(float r, float g, float b) | |||
| { | |||
| if (r < 0.0f || r > 1.0f) | |||
| @@ -18,16 +18,28 @@ namespace Discord | |||
| /// </summary> | |||
| Color Color { get; } | |||
| /// <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> | |||
| /// <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; } | |||
| /// <summary> | |||
| /// Returns <see langword="true"/> if this role is automatically managed by Discord. | |||
| /// Determines whether the role is managed by Discord. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// Returns <see langword="true"/> if this role is automatically managed by Discord; otherwise, returns | |||
| /// <see langword="false"/>. | |||
| /// </returns> | |||
| bool IsManaged { get; } | |||
| /// <summary> | |||
| /// Returns <see langword="true"/> if this role may be mentioned in messages. | |||
| /// Determines whether the role is mentionable. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// Returns <see langword="true"/> if this role may be mentioned in messages; otherwise, returns | |||
| /// <see langword="false"/>. | |||
| /// </returns> | |||
| bool IsMentionable { get; } | |||
| /// <summary> | |||
| /// Gets the name of this role. | |||
| @@ -45,6 +57,8 @@ namespace Discord | |||
| /// <summary> | |||
| /// Modifies this role. | |||
| /// </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); | |||
| } | |||
| } | |||
| @@ -5,7 +5,7 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a Discord user that is in a guild. | |||
| /// Represents a generic guild user. | |||
| /// </summary> | |||
| public interface IGuildUser : IUser, IVoiceState | |||
| { | |||
| @@ -46,31 +46,38 @@ namespace Discord | |||
| /// Kicks this user from this guild. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Modifies this user's properties in this guild. | |||
| /// </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); | |||
| /// <summary> | |||
| /// Adds a <paramref name="role"/> to this user in this guild. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Adds <paramref name="roles"/> to this user in this guild. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Removes a <paramref name="role"/> from this user in this guild. | |||
| /// </summary> | |||
| /// <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); | |||
| /// <summary> | |||
| /// Removes <paramref name="roles"/> from this user in this guild. | |||
| /// </summary> | |||
| /// <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); | |||
| } | |||
| } | |||
| @@ -3,7 +3,7 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a Discord user. | |||
| /// Represents a generic user. | |||
| /// </summary> | |||
| public interface IUser : ISnowflakeEntity, IMentionable, IPresence | |||
| { | |||
| @@ -41,8 +41,7 @@ namespace Discord | |||
| string Username { get; } | |||
| /// <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> | |||
| Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | |||
| } | |||
| @@ -26,7 +26,7 @@ namespace Discord | |||
| /// </summary> | |||
| bool IsSuppressed { get; } | |||
| /// <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> | |||
| IVoiceChannel VoiceChannel { get; } | |||
| /// <summary> | |||
| @@ -30,6 +30,7 @@ namespace Discord | |||
| 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> | |||
| /// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception> | |||
| public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) | |||
| { | |||
| if (embed.Type != EmbedType.Rich) | |||
| @@ -5,6 +5,9 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a generic Discord client. | |||
| /// </summary> | |||
| public interface IDiscordClient : IDisposable | |||
| { | |||
| ConnectionState ConnectionState { get; } | |||
| @@ -14,11 +17,37 @@ namespace Discord | |||
| Task StartAsync(); | |||
| Task StopAsync(); | |||
| /// <summary> | |||
| /// Gets the application information associated with this account. | |||
| /// </summary> | |||
| 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); | |||
| /// <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); | |||
| /// <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); | |||
| /// <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<IConnection>> GetConnectionsAsync(RequestOptions options = null); | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.Logging | |||
| @@ -24,7 +24,10 @@ namespace Discord.Logging | |||
| if (severity <= Level) | |||
| 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) | |||
| { | |||
| @@ -33,7 +36,10 @@ namespace Discord.Logging | |||
| if (severity <= Level) | |||
| await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); | |||
| } | |||
| catch { } | |||
| catch | |||
| { | |||
| // ignored | |||
| } | |||
| } | |||
| #if FORMATSTR | |||
| public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) | |||
| @@ -44,7 +44,7 @@ namespace Discord | |||
| /// <summary> | |||
| /// Initializes a new <see cref="RequestOptions" /> class with the default request timeout set in | |||
| /// <see cref="DiscordConfig" /> . | |||
| /// <see cref="DiscordConfig" />. | |||
| /// </summary> | |||
| public RequestOptions() | |||
| { | |||
| @@ -31,11 +31,12 @@ namespace Discord | |||
| /// <summary> | |||
| /// Parses a provided user mention string. | |||
| /// </summary> | |||
| /// <exception cref="ArgumentException">Invalid mention format.</exception> | |||
| public static ulong ParseUser(string text) | |||
| { | |||
| if (TryParseUser(text, out ulong id)) | |||
| return id; | |||
| throw new ArgumentException("Invalid mention format", nameof(text)); | |||
| throw new ArgumentException("Invalid mention format.", nameof(text)); | |||
| } | |||
| /// <summary> | |||
| /// Tries to parse a provided user mention string. | |||
| @@ -59,11 +60,12 @@ namespace Discord | |||
| /// <summary> | |||
| /// Parses a provided channel mention string. | |||
| /// </summary> | |||
| /// <exception cref="ArgumentException">Invalid mention format.</exception> | |||
| public static ulong ParseChannel(string text) | |||
| { | |||
| if (TryParseChannel(text, out ulong id)) | |||
| return id; | |||
| throw new ArgumentException("Invalid mention format", nameof(text)); | |||
| throw new ArgumentException("Invalid mention format.", nameof(text)); | |||
| } | |||
| /// <summary> | |||
| /// Tries to parse a provided channel mention string. | |||
| @@ -84,11 +86,12 @@ namespace Discord | |||
| /// <summary> | |||
| /// Parses a provided role mention string. | |||
| /// </summary> | |||
| /// <exception cref="ArgumentException">Invalid mention format.</exception> | |||
| public static ulong ParseRole(string text) | |||
| { | |||
| if (TryParseRole(text, out ulong id)) | |||
| return id; | |||
| throw new ArgumentException("Invalid mention format", nameof(text)); | |||
| throw new ArgumentException("Invalid mention format.", nameof(text)); | |||
| } | |||
| /// <summary> | |||
| /// Tries to parse a provided role mention string. | |||
| @@ -163,22 +166,22 @@ namespace Discord | |||
| if (user != null) | |||
| return $"@{guildUser?.Nickname ?? user?.Username}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.NameNoPrefix: | |||
| if (user != null) | |||
| return $"{guildUser?.Nickname ?? user?.Username}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.FullName: | |||
| if (user != null) | |||
| return $"@{user.Username}#{user.Discriminator}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.FullNameNoPrefix: | |||
| if (user != null) | |||
| return $"{user.Username}#{user.Discriminator}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.Sanitize: | |||
| if (guildUser != null && guildUser.Nickname == null) | |||
| return MentionUser($"{SanitizeChar}{tag.Key}", false); | |||
| @@ -200,13 +203,13 @@ namespace Discord | |||
| if (channel != null) | |||
| return $"#{channel.Name}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.NameNoPrefix: | |||
| case TagHandling.FullNameNoPrefix: | |||
| if (channel != null) | |||
| return $"{channel.Name}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.Sanitize: | |||
| return MentionChannel($"{SanitizeChar}{tag.Key}"); | |||
| } | |||
| @@ -225,13 +228,13 @@ namespace Discord | |||
| if (role != null) | |||
| return $"@{role.Name}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.NameNoPrefix: | |||
| case TagHandling.FullNameNoPrefix: | |||
| if (role != null) | |||
| return $"{role.Name}"; | |||
| else | |||
| return $""; | |||
| return ""; | |||
| case TagHandling.Sanitize: | |||
| return MentionRole($"{SanitizeChar}{tag.Key}"); | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| namespace Discord | |||
| { | |||
| @@ -86,7 +86,7 @@ namespace Discord | |||
| 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); | |||
| } | |||
| @@ -109,7 +109,7 @@ namespace Discord | |||
| 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); | |||
| } | |||
| @@ -132,7 +132,7 @@ namespace Discord | |||
| 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); | |||
| } | |||
| @@ -155,7 +155,7 @@ namespace Discord | |||
| 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); | |||
| } | |||
| @@ -178,11 +178,12 @@ namespace Discord | |||
| 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); | |||
| } | |||
| // Bulk Delete | |||
| /// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks..</exception> | |||
| public static void YoungerThanTwoWeeks(ulong[] collection, string name) | |||
| { | |||
| var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); | |||
| @@ -193,12 +194,13 @@ namespace Discord | |||
| throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | |||
| } | |||
| } | |||
| /// <exception cref="ArgumentException">The everyone role cannot be assigned to a user</exception> | |||
| public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) | |||
| { | |||
| for (var i = 0; i < roles.Length; i++) | |||
| { | |||
| 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 LogManager LogManager { get; } | |||
| /// <summary> | |||
| /// Gets the login state of the client. | |||
| /// </summary> | |||
| public LoginState LoginState { get; private set; } | |||
| /// <summary> | |||
| /// Gets the logged-in user. | |||
| /// </summary> | |||
| public ISelfUser CurrentUser { get; protected set; } | |||
| /// <summary> | |||
| /// Gets the type of the authentication token. | |||
| /// </summary> | |||
| 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) | |||
| { | |||
| ApiClient = client; | |||
| @@ -48,8 +57,7 @@ namespace Discord.Rest | |||
| }; | |||
| 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) | |||
| { | |||
| await _stateLock.WaitAsync().ConfigureAwait(false); | |||
| @@ -87,8 +95,7 @@ namespace Discord.Rest | |||
| } | |||
| internal virtual Task OnLoginAsync(TokenType tokenType, string token) | |||
| => Task.Delay(0); | |||
| /// <inheritdoc /> | |||
| public async Task LogoutAsync() | |||
| { | |||
| await _stateLock.WaitAsync().ConfigureAwait(false); | |||
| @@ -130,49 +137,68 @@ namespace Discord.Rest | |||
| => ClientHelper.GetRecommendShardCountAsync(this, options); | |||
| //IDiscordClient | |||
| /// <inheritdoc /> | |||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | |||
| /// <inheritdoc /> | |||
| ISelfUser IDiscordClient.CurrentUser => CurrentUser; | |||
| /// <inheritdoc /> | |||
| Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
| => throw new NotSupportedException(); | |||
| /// <inheritdoc /> | |||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IChannel>(null); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(ImmutableArray.Create<IPrivateChannel>()); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IDMChannel>>(ImmutableArray.Create<IDMChannel>()); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IGroupChannel>>(ImmutableArray.Create<IGroupChannel>()); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>()); | |||
| /// <inheritdoc /> | |||
| Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
| => Task.FromResult<IInvite>(null); | |||
| /// <inheritdoc /> | |||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IGuild>(null); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>()); | |||
| /// <inheritdoc /> | |||
| Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
| => throw new NotSupportedException(); | |||
| /// <inheritdoc /> | |||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(null); | |||
| /// <inheritdoc /> | |||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | |||
| => Task.FromResult<IUser>(null); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(ImmutableArray.Create<IVoiceRegion>()); | |||
| /// <inheritdoc /> | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(null); | |||
| /// <inheritdoc /> | |||
| Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | |||
| => Task.FromResult<IWebhook>(null); | |||
| /// <inheritdoc /> | |||
| Task IDiscordClient.StartAsync() | |||
| => Task.Delay(0); | |||
| /// <inheritdoc /> | |||
| Task IDiscordClient.StopAsync() | |||
| => Task.Delay(0); | |||
| } | |||
| @@ -33,65 +33,49 @@ namespace Discord.Rest | |||
| _applicationInfo = null; | |||
| return Task.Delay(0); | |||
| } | |||
| /// <inheritdoc /> | |||
| public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | |||
| { | |||
| return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false)); | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task<RestChannel> GetChannelAsync(ulong id, RequestOptions options = null) | |||
| => ClientHelper.GetChannelAsync(this, id, options); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetPrivateChannelsAsync(this, options); | |||
| public Task<IReadOnlyCollection<RestDMChannel>> GetDMChannelsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetDMChannelsAsync(this, options); | |||
| public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetGroupChannelsAsync(this, options); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetConnectionsAsync(this, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
| => ClientHelper.GetInviteAsync(this, inviteId, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null) | |||
| => ClientHelper.GetGuildAsync(this, id, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null) | |||
| => ClientHelper.GetGuildEmbedAsync(this, id, options); | |||
| /// <inheritdoc /> | |||
| public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) | |||
| => ClientHelper.GetGuildSummariesAsync(this, null, null, options); | |||
| /// <inheritdoc /> | |||
| public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null) | |||
| => ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetGuildsAsync(this, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | |||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestUser> GetUserAsync(ulong id, RequestOptions options = null) | |||
| => ClientHelper.GetUserAsync(this, id, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuildUser> GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null) | |||
| => ClientHelper.GetGuildUserAsync(this, guildId, id, options); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetVoiceRegionsAsync(this, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | |||
| => ClientHelper.GetVoiceRegionAsync(this, id, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | |||
| => ClientHelper.GetWebhookAsync(this, id, options); | |||
| @@ -1,7 +1,10 @@ | |||
| using Discord.Net.Rest; | |||
| using Discord.Net.Rest; | |||
| namespace Discord.Rest | |||
| { | |||
| /// <summary> | |||
| /// Represents a configuration class for <see cref="DiscordRestClient" />. | |||
| /// </summary> | |||
| public class DiscordRestConfig : DiscordConfig | |||
| { | |||
| /// <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, | |||
| RequestOptions options) | |||
| => new TypingNotifier(client, channel, options); | |||
| => new TypingNotifier(channel, options); | |||
| //Webhooks | |||
| 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 | |||
| { | |||
| /// <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 | |||
| { | |||
| private long _createdAtTicks; | |||
| @@ -48,6 +48,7 @@ namespace Discord.Rest | |||
| _createdAtTicks = model.CreatedAt.UtcTicks; | |||
| } | |||
| /// <inheritdoc /> | |||
| IUser IInviteMetadata.Inviter => Inviter; | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| using System.IO; | |||
| using Newtonsoft.Json; | |||
| using Model = Discord.API.Image; | |||
| @@ -13,6 +13,7 @@ namespace Discord.Net.Converters | |||
| public override bool CanRead => 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) | |||
| { | |||
| throw new InvalidOperationException(); | |||
| @@ -1,4 +1,4 @@ | |||
| using Newtonsoft.Json; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| namespace Discord.Net.Converters | |||
| @@ -11,6 +11,7 @@ namespace Discord.Net.Converters | |||
| public override bool CanRead => true; | |||
| public override bool CanWrite => true; | |||
| /// <exception cref="JsonSerializationException">Unknown permission target.</exception> | |||
| public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||
| { | |||
| switch ((string)reader.Value) | |||
| @@ -20,10 +21,11 @@ namespace Discord.Net.Converters | |||
| case "role": | |||
| return PermissionTarget.Role; | |||
| 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) | |||
| { | |||
| switch ((PermissionTarget)value) | |||
| @@ -35,7 +37,7 @@ namespace Discord.Net.Converters | |||
| writer.WriteValue("role"); | |||
| break; | |||
| 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; | |||
| #if DEBUG_LIMITS | |||
| using System.Diagnostics; | |||
| @@ -117,7 +117,7 @@ namespace Discord.Net.Queue | |||
| if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) | |||
| _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) { } | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| @@ -6,21 +6,19 @@ namespace Discord.Rest | |||
| { | |||
| internal class TypingNotifier : IDisposable | |||
| { | |||
| private readonly BaseDiscordClient _client; | |||
| private readonly CancellationTokenSource _cancelToken; | |||
| private readonly IMessageChannel _channel; | |||
| private readonly RequestOptions _options; | |||
| public TypingNotifier(BaseDiscordClient discord, IMessageChannel channel, RequestOptions options) | |||
| public TypingNotifier(IMessageChannel channel, RequestOptions options) | |||
| { | |||
| _client = discord; | |||
| _cancelToken = new CancellationTokenSource(); | |||
| _channel = channel; | |||
| _options = options; | |||
| var _ = Run(); | |||
| _ = RunAsync(); | |||
| } | |||
| private async Task Run() | |||
| private async Task RunAsync() | |||
| { | |||
| try | |||
| { | |||
| @@ -31,7 +29,11 @@ namespace Discord.Rest | |||
| { | |||
| await _channel.TriggerTypingAsync(_options).ConfigureAwait(false); | |||
| } | |||
| catch { } | |||
| catch | |||
| { | |||
| // ignored | |||
| } | |||
| 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.Logging; | |||
| using Discord.Net.Converters; | |||
| @@ -65,7 +65,7 @@ namespace Discord.Audio | |||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||
| 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.ReceivedEvent += ProcessMessageAsync; | |||
| ApiClient.ReceivedPacket += ProcessPacketAsync; | |||
| @@ -291,7 +291,7 @@ namespace Discord.Audio | |||
| { | |||
| if (packet.Length != 70) | |||
| { | |||
| await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); | |||
| await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); | |||
| return; | |||
| } | |||
| string ip; | |||
| @@ -303,7 +303,7 @@ namespace Discord.Audio | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); | |||
| await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| @@ -343,7 +343,7 @@ namespace Discord.Audio | |||
| { | |||
| if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) | |||
| { | |||
| await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); | |||
| await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); | |||
| return; | |||
| } | |||
| if (!_ssrcMap.TryGetValue(ssrc, out var userId)) | |||
| @@ -362,7 +362,7 @@ namespace Discord.Audio | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); | |||
| await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | |||
| @@ -371,7 +371,7 @@ namespace Discord.Audio | |||
| } | |||
| 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; | |||
| } | |||
| } | |||
| @@ -116,7 +116,7 @@ namespace Discord.Audio.Streams | |||
| timestamp += OpusEncoder.FrameSamplesPerChannel; | |||
| } | |||
| #if DEBUG | |||
| var _ = _logger?.DebugAsync($"Buffer underrun"); | |||
| var _ = _logger?.DebugAsync("Buffer underrun"); | |||
| #endif | |||
| } | |||
| } | |||
| @@ -140,7 +140,7 @@ namespace Discord.Audio.Streams | |||
| if (!_bufferPool.TryDequeue(out byte[] buffer)) | |||
| { | |||
| #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 | |||
| return; | |||
| } | |||
| @@ -149,7 +149,7 @@ namespace Discord.Audio.Streams | |||
| if (!_isPreloaded && _queuedFrames.Count == _queueLength) | |||
| { | |||
| #if DEBUG | |||
| var _ = _logger?.DebugAsync($"Preloaded"); | |||
| var _ = _logger?.DebugAsync("Preloaded"); | |||
| #endif | |||
| _isPreloaded = true; | |||
| } | |||
| @@ -173,4 +173,4 @@ namespace Discord.Audio.Streams | |||
| return Task.Delay(0); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -26,18 +26,12 @@ namespace Discord.WebSocket | |||
| : base(config, client) => _baseconfig = config; | |||
| private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
| => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||
| /// <inheritdoc /> | |||
| public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null); | |||
| /// <inheritdoc /> | |||
| public abstract SocketUser GetUser(ulong id); | |||
| /// <inheritdoc /> | |||
| public abstract SocketUser GetUser(string username, string discriminator); | |||
| /// <inheritdoc /> | |||
| public abstract SocketChannel GetChannel(ulong id); | |||
| /// <inheritdoc /> | |||
| public abstract SocketGuild GetGuild(ulong id); | |||
| /// <inheritdoc /> | |||
| public abstract RestVoiceRegion GetVoiceRegion(string id); | |||
| /// <inheritdoc /> | |||
| 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 SetActivityAsync(IActivity activity); | |||
| public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | |||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); | |||
| /// <inheritdoc /> | |||
| public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
| => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | |||
| // IDiscordClient | |||
| /// <inheritdoc /> | |||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
| => await GetApplicationInfoAsync(options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IChannel>(GetChannel(id)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
| => await GetConnectionsAsync(options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
| => await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IGuild>(GetGuild(id)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | |||
| /// <inheritdoc /> | |||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
| => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); | |||
| /// <inheritdoc /> | |||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
| /// <inheritdoc /> | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => 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> | |||
| 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; } | |||
| /// <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; } | |||
| /// <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; } | |||
| /// <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; } | |||
| /// <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; } | |||
| /// <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; | |||
| /// <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) | |||
| { | |||
| Client = client; | |||
| @@ -19,24 +19,29 @@ namespace Discord.WebSocket | |||
| private int _totalShards; | |||
| 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 { } } | |||
| /// <inheritdoc /> | |||
| public override UserStatus Status { get => _shards[0].Status; protected set { } } | |||
| /// <inheritdoc /> | |||
| public override IActivity Activity { get => _shards[0].Activity; protected set { } } | |||
| 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; | |||
| /// <inheritdoc /> | |||
| 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()) { } | |||
| /// <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)) { } | |||
| /// <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()) { } | |||
| /// <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)) { } | |||
| private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client) | |||
| : base(config, client) | |||
| @@ -213,7 +218,9 @@ namespace Discord.WebSocket | |||
| public override RestVoiceRegion GetVoiceRegion(string 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) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| @@ -233,11 +240,13 @@ namespace Discord.WebSocket | |||
| return (int)Math.Round(total / (double)_shards.Length); | |||
| } | |||
| /// <inheritdoc /> | |||
| public override async Task SetStatusAsync(UserStatus status) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| await _shards[i].SetStatusAsync(status).ConfigureAwait(false); | |||
| } | |||
| /// <inheritdoc /> | |||
| public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) | |||
| { | |||
| IActivity activity = null; | |||
| @@ -247,6 +256,7 @@ namespace Discord.WebSocket | |||
| activity = new Game(name, type); | |||
| await SetActivityAsync(activity).ConfigureAwait(false); | |||
| } | |||
| /// <inheritdoc /> | |||
| public override async Task SetActivityAsync(IActivity activity) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| @@ -316,34 +326,46 @@ namespace Discord.WebSocket | |||
| } | |||
| //IDiscordClient | |||
| /// <inheritdoc /> | |||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
| => await GetApplicationInfoAsync().ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IChannel>(GetChannel(id)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
| => await GetConnectionsAsync().ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
| => await GetInviteAsync(inviteId).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IGuild>(GetGuild(id)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | |||
| /// <inheritdoc /> | |||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
| => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); | |||
| /// <inheritdoc /> | |||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
| /// <inheritdoc /> | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
| } | |||
| @@ -508,7 +508,7 @@ namespace Discord.WebSocket | |||
| { | |||
| type = "GUILD_AVAILABLE"; | |||
| _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); | |||
| if (guild != null) | |||
| @@ -533,7 +533,7 @@ namespace Discord.WebSocket | |||
| } | |||
| else | |||
| { | |||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||
| var guild = AddGuild(data, State); | |||
| if (guild != null) | |||
| @@ -614,7 +614,7 @@ namespace Discord.WebSocket | |||
| if (data.Unavailable == true) | |||
| { | |||
| 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); | |||
| if (guild != null) | |||
| @@ -630,7 +630,7 @@ namespace Discord.WebSocket | |||
| } | |||
| else | |||
| { | |||
| await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||
| var guild = RemoveGuild(data.Id); | |||
| if (guild != null) | |||
| @@ -1,41 +1,72 @@ | |||
| using Discord.Net.Udp; | |||
| using Discord.Net.Udp; | |||
| using Discord.Net.WebSockets; | |||
| using Discord.Rest; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a configuration class for <see cref="DiscordSocketClient" />. | |||
| /// </summary> | |||
| public class DiscordSocketConfig : DiscordRestConfig | |||
| { | |||
| /// <summary> | |||
| /// Gets or sets the encoding gateway should use. | |||
| /// </summary> | |||
| 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; | |||
| /// <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; | |||
| /// <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; | |||
| /// <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; | |||
| /// <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; | |||
| /// <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> | |||
| 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; } | |||
| /// <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; } | |||
| /// <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; | |||
| /// <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; | |||
| /// <summary> | |||
| /// Initializes a default configuration. | |||
| /// </summary> | |||
| public DiscordSocketConfig() | |||
| { | |||
| 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 | |||
| { | |||
| } | |||
| @@ -5,18 +5,52 @@ using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a generic WebSocket-based channel that can send and receive messages. | |||
| /// </summary> | |||
| public interface ISocketMessageChannel : IMessageChannel | |||
| { | |||
| /// <summary> Gets all messages in this channel's cache. </summary> | |||
| 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); | |||
| #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); | |||
| #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); | |||
| SocketMessage GetCachedMessage(ulong id); | |||
| @@ -26,7 +60,13 @@ namespace Discord.WebSocket | |||
| IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | |||
| /// <summary> Gets a collection of messages in this channel. </summary> | |||
| 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); | |||
| } | |||
| } | |||
| @@ -1,7 +1,10 @@ | |||
| using System.Collections.Generic; | |||
| using System.Collections.Generic; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a generic WebSocket-based channel that is private to select recipients. | |||
| /// </summary> | |||
| public interface ISocketPrivateChannel : IPrivateChannel | |||
| { | |||
| new IReadOnlyCollection<SocketUser> Recipients { get; } | |||
| @@ -3,14 +3,14 @@ using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Diagnostics; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using Discord.Audio; | |||
| using Discord.Rest; | |||
| using Model = Discord.API.Channel; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a WebSocket-based category channel. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel | |||
| { | |||
| @@ -1,4 +1,3 @@ | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Diagnostics; | |||
| @@ -8,16 +7,27 @@ using Model = Discord.API.Channel; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a WebSocket-based channel. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public abstract class SocketChannel : SocketEntity<ulong>, IChannel | |||
| { | |||
| /// <summary> | |||
| /// Gets when the channel is created. | |||
| /// </summary> | |||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
| /// <summary> | |||
| /// Gets a collection of users from the WebSocket cache. | |||
| /// </summary> | |||
| public IReadOnlyCollection<SocketUser> Users => GetUsersInternal(); | |||
| internal SocketChannel(DiscordSocketClient discord, ulong id) | |||
| : base(discord, id) | |||
| { | |||
| } | |||
| /// <exception cref="InvalidOperationException">Unexpected channel type is created.</exception> | |||
| internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) | |||
| { | |||
| switch (model.Type) | |||
| @@ -33,6 +43,17 @@ namespace Discord.WebSocket | |||
| internal abstract void Update(ClientState state, Model model); | |||
| //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); | |||
| internal abstract SocketUser GetUserInternal(ulong id); | |||
| internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal(); | |||
| @@ -40,10 +61,13 @@ namespace Discord.WebSocket | |||
| internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; | |||
| //IChannel | |||
| /// <inheritdoc /> | |||
| string IChannel.Name => null; | |||
| /// <inheritdoc /> | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(null); //Overridden | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden | |||
| } | |||
| @@ -10,13 +10,17 @@ using Model = Discord.API.Channel; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a WebSocket-based direct-message channel. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | |||
| { | |||
| 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 new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||
| @@ -39,10 +43,12 @@ namespace Discord.WebSocket | |||
| Recipient.Update(state, model.Recipients.Value[0]); | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task CloseAsync(RequestOptions options = null) | |||
| => ChannelHelper.DeleteAsync(this, Discord, options); | |||
| //Messages | |||
| /// <inheritdoc /> | |||
| public SocketMessage GetCachedMessage(ulong id) | |||
| => _messages?.Get(id); | |||
| 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); | |||
| 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); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
| #if FILESYSTEM | |||
| /// <inheritdoc /> | |||
| 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); | |||
| #endif | |||
| /// <inheritdoc /> | |||
| 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); | |||
| /// <inheritdoc /> | |||
| public Task TriggerTypingAsync(RequestOptions options = null) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| => ChannelHelper.EnterTypingState(this, Discord, options); | |||
| @@ -97,24 +112,33 @@ namespace Discord.WebSocket | |||
| return null; | |||
| } | |||
| /// <summary> | |||
| /// Returns the recipient user. | |||
| /// </summary> | |||
| public override string ToString() => $"@{Recipient}"; | |||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | |||
| internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | |||
| //SocketChannel | |||
| /// <inheritdoc /> | |||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | |||
| /// <inheritdoc /> | |||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | |||
| //IDMChannel | |||
| //IDMChannel | |||
| /// <inheritdoc /> | |||
| IUser IDMChannel.Recipient => Recipient; | |||
| //ISocketPrivateChannel | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||
| //IPrivateChannel | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | |||
| //IMessageChannel | |||
| /// <inheritdoc /> | |||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| { | |||
| if (mode == CacheMode.AllowDownload) | |||
| @@ -122,30 +146,41 @@ namespace Discord.WebSocket | |||
| else | |||
| return GetCachedMessage(id); | |||
| } | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions 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) | |||
| => 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) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | |||
| => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | |||
| #if FILESYSTEM | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||
| => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
| #endif | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
| => EnterTypingState(options); | |||
| //IChannel | |||
| //IChannel | |||
| /// <inheritdoc /> | |||
| string IChannel.Name => $"@{Recipient}"; | |||
| /// <inheritdoc /> | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||
| } | |||
| @@ -15,7 +15,7 @@ using VoiceStateModel = Discord.API.VoiceState; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a private WebSocket group channel. | |||
| /// Represents a WebSocket-based private group channel. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | |||
| @@ -24,10 +24,12 @@ namespace Discord.WebSocket | |||
| private string _iconId; | |||
| private ConcurrentDictionary<ulong, SocketGroupUser> _users; | |||
| private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
| private readonly ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
| /// <inheritdoc /> | |||
| public string Name { get; private set; } | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||
| public new IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection(); | |||
| public IReadOnlyCollection<SocketGroupUser> Recipients | |||
| @@ -65,15 +67,18 @@ namespace Discord.WebSocket | |||
| _users = users; | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task LeaveAsync(RequestOptions options = null) | |||
| => ChannelHelper.DeleteAsync(this, Discord, options); | |||
| /// <exception cref="NotSupportedException">Voice is not yet supported for group channels.</exception> | |||
| public Task<IAudioClient> ConnectAsync() | |||
| { | |||
| throw new NotSupportedException("Voice is not yet supported for group channels."); | |||
| } | |||
| //Messages | |||
| /// <inheritdoc /> | |||
| public SocketMessage GetCachedMessage(ulong id) | |||
| => _messages?.Get(id); | |||
| 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); | |||
| 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); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
| #if FILESYSTEM | |||
| /// <inheritdoc /> | |||
| 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); | |||
| #endif | |||
| /// <inheritdoc /> | |||
| 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); | |||
| /// <inheritdoc /> | |||
| public Task TriggerTypingAsync(RequestOptions options = null) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| => ChannelHelper.EnterTypingState(this, Discord, options); | |||
| @@ -118,6 +132,17 @@ namespace Discord.WebSocket | |||
| => _messages?.Remove(id); | |||
| //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) | |||
| { | |||
| if (_users.TryGetValue(id, out SocketGroupUser user)) | |||
| @@ -167,21 +192,29 @@ namespace Discord.WebSocket | |||
| return null; | |||
| } | |||
| /// <summary> | |||
| /// Returns the name of the group. | |||
| /// </summary> | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"{Name} ({Id}, Group)"; | |||
| internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; | |||
| //SocketChannel | |||
| /// <inheritdoc /> | |||
| internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users; | |||
| /// <inheritdoc /> | |||
| internal override SocketUser GetUserInternal(ulong id) => GetUser(id); | |||
| //ISocketPrivateChannel | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => Recipients; | |||
| //IPrivateChannel | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | |||
| //IMessageChannel | |||
| /// <inheritdoc /> | |||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| { | |||
| if (mode == CacheMode.AllowDownload) | |||
| @@ -189,31 +222,42 @@ namespace Discord.WebSocket | |||
| else | |||
| return GetCachedMessage(id); | |||
| } | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions 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) | |||
| => 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) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | |||
| => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | |||
| #if FILESYSTEM | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||
| => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
| #endif | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
| => EnterTypingState(options); | |||
| //IAudioChannel | |||
| /// <inheritdoc /> | |||
| Task<IAudioClient> IAudioChannel.ConnectAsync(Action<IAudioClient> configAction) { throw new NotSupportedException(); } | |||
| //IChannel | |||
| /// <inheritdoc /> | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||
| } | |||
| @@ -9,12 +9,20 @@ using Model = Discord.API.Channel; | |||
| 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}")] | |||
| public class SocketGuildChannel : SocketChannel, IGuildChannel | |||
| { | |||
| 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; } | |||
| /// <inheritdoc /> | |||
| public string Name { get; private set; } | |||
| @@ -22,11 +30,23 @@ namespace Discord.WebSocket | |||
| public int Position { get; private set; } | |||
| /// <inheritdoc /> | |||
| 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 | |||
| => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; | |||
| /// <inheritdoc /> | |||
| 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>(); | |||
| internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | |||
| @@ -131,7 +151,11 @@ namespace Discord.WebSocket | |||
| public new virtual SocketGuildUser GetUser(ulong id) => null; | |||
| /// <summary> | |||
| /// Gets the name of the channel. | |||
| /// </summary> | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"{Name} ({Id}, Guild)"; | |||
| internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; | |||
| //SocketChannel | |||
| @@ -10,6 +10,9 @@ using Model = Discord.API.Channel; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a WebSocket-based channel in a guild that can send and receive messages. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel | |||
| { | |||
| @@ -73,12 +76,16 @@ namespace Discord.WebSocket | |||
| => 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) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
| @@ -104,6 +111,7 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public Task TriggerTypingAsync(RequestOptions options = null) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| => ChannelHelper.EnterTypingState(this, Discord, options); | |||
| @@ -128,10 +136,34 @@ namespace Discord.WebSocket | |||
| } | |||
| //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) | |||
| => 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) | |||
| => 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) | |||
| => ChannelHelper.GetWebhooksAsync(this, Discord, options); | |||
| @@ -1,4 +1,4 @@ | |||
| using Discord.Audio; | |||
| using Discord.Audio; | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| @@ -10,12 +10,18 @@ using Model = Discord.API.Channel; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a WebSocket-based voice channel in a guild. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel | |||
| { | |||
| /// <inheritdoc /> | |||
| public int Bitrate { get; private set; } | |||
| /// <inheritdoc /> | |||
| public int? UserLimit { get; private set; } | |||
| /// <inheritdoc /> | |||
| public override IReadOnlyCollection<SocketGuildUser> Users | |||
| => 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; | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) | |||
| => ChannelHelper.ModifyAsync(this, Discord, func, options); | |||
| /// <inheritdoc /> | |||
| public async Task<IAudioClient> ConnectAsync(Action<IAudioClient> configAction = null) | |||
| { | |||
| return await Guild.ConnectAudioAsync(Id, false, false, configAction).ConfigureAwait(false); | |||
| } | |||
| /// <inheritdoc /> | |||
| public override SocketGuildUser GetUser(ulong id) | |||
| { | |||
| var user = Guild.GetUser(id); | |||
| @@ -57,8 +66,10 @@ namespace Discord.WebSocket | |||
| internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; | |||
| //IGuildChannel | |||
| /// <inheritdoc /> | |||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||
| } | |||
| @@ -21,6 +21,10 @@ using VoiceStateModel = Discord.API.VoiceState; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a WebSocket-based guild object. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketGuild : SocketEntity<ulong>, IGuild | |||
| { | |||
| private readonly SemaphoreSlim _audioLock; | |||
| @@ -46,11 +50,13 @@ namespace Discord.WebSocket | |||
| public MfaLevel MfaLevel { get; private set; } | |||
| /// <inheritdoc /> | |||
| 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; } | |||
| /// <summary> Gets the number of members downloaded to the local guild cache. </summary> | |||
| public int DownloadedMemberCount { get; private set; } | |||
| @@ -84,11 +90,23 @@ namespace Discord.WebSocket | |||
| public bool IsSynced => _syncPromise.Task.IsCompleted; | |||
| public Task SyncPromise => _syncPromise.Task; | |||
| public Task DownloaderPromise => _downloaderPromise.Task; | |||
| /// <summary> | |||
| /// Returns the <see cref="IAudioClient" /> associated with this guild. | |||
| /// </summary> | |||
| 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 | |||
| .Where(c => CurrentUser.GetPermissions(c).ViewChannel) | |||
| .OrderBy(c => c.Position) | |||
| .FirstOrDefault(); | |||
| /// <summary> | |||
| /// Returns the AFK voice channel, or <see langword="null" /> if none is set. | |||
| /// </summary> | |||
| public SocketVoiceChannel AFKChannel | |||
| { | |||
| get | |||
| @@ -97,6 +115,9 @@ namespace Discord.WebSocket | |||
| 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 | |||
| { | |||
| get | |||
| @@ -105,6 +126,9 @@ namespace Discord.WebSocket | |||
| 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 | |||
| { | |||
| get | |||
| @@ -113,14 +137,32 @@ namespace Discord.WebSocket | |||
| return id.HasValue ? GetTextChannel(id.Value) : null; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Returns a collection of text channels present in this guild. | |||
| /// </summary> | |||
| public IReadOnlyCollection<SocketTextChannel> TextChannels | |||
| => 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 | |||
| => 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 | |||
| => 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; | |||
| /// <summary> | |||
| /// Returns the @everyone role in this guild. | |||
| /// </summary> | |||
| public SocketRole EveryoneRole => GetRole(Id); | |||
| /// <summary> | |||
| /// Returns a collection of channels present in this guild. | |||
| /// </summary> | |||
| public IReadOnlyCollection<SocketGuildChannel> Channels | |||
| { | |||
| get | |||
| @@ -130,9 +172,26 @@ namespace Discord.WebSocket | |||
| 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; | |||
| /// <summary> | |||
| /// Gets a collection of features enabled in this guild. | |||
| /// </summary> | |||
| 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(); | |||
| /// <summary> | |||
| /// Gets a collection of roles in this guild. | |||
| /// </summary> | |||
| public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection(); | |||
| internal SocketGuild(DiscordSocketClient client, ulong id) | |||
| @@ -315,7 +374,13 @@ namespace Discord.WebSocket | |||
| => GuildHelper.LeaveAsync(this, Discord, options); | |||
| //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) | |||
| => GuildHelper.GetBansAsync(this, Discord, options); | |||
| @@ -334,6 +399,13 @@ namespace Discord.WebSocket | |||
| => GuildHelper.RemoveBanAsync(this, Discord, userId, options); | |||
| //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) | |||
| { | |||
| var channel = Discord.State.GetChannel(id) as SocketGuildChannel; | |||
| @@ -341,14 +413,52 @@ namespace Discord.WebSocket | |||
| return channel; | |||
| 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) | |||
| => 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) | |||
| => 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) | |||
| => 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) | |||
| => 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) | |||
| => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); | |||
| @@ -373,16 +483,43 @@ namespace Discord.WebSocket | |||
| => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); | |||
| //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) | |||
| => GuildHelper.GetInvitesAsync(this, Discord, options); | |||
| //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) | |||
| { | |||
| if (_roles.TryGetValue(id, out SocketRole value)) | |||
| return value; | |||
| 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?), | |||
| bool isHoisted = false, RequestOptions options = null) | |||
| => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); | |||
| @@ -400,12 +537,20 @@ namespace Discord.WebSocket | |||
| } | |||
| //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) | |||
| { | |||
| if (_members.TryGetValue(id, out SocketGuildUser member)) | |||
| return member; | |||
| return null; | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) | |||
| => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); | |||
| @@ -459,6 +604,9 @@ namespace Discord.WebSocket | |||
| return null; | |||
| } | |||
| /// <summary> | |||
| /// Downloads the users of this guild to the WebSocket cache. | |||
| /// </summary> | |||
| public async Task DownloadUsersAsync() | |||
| { | |||
| await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); | |||
| @@ -469,8 +617,23 @@ namespace Discord.WebSocket | |||
| } | |||
| //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) | |||
| => 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) | |||
| => GuildHelper.GetWebhooksAsync(this, Discord, options); | |||
| @@ -592,7 +755,7 @@ namespace Discord.WebSocket | |||
| try | |||
| { | |||
| 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(); | |||
| 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; | |||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||
| internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; | |||
| @@ -12,7 +12,9 @@ using PresenceModel = Discord.API.Presence; | |||
| 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}")] | |||
| public class SocketGuildUser : SocketUser, IGuildUser | |||
| { | |||
| @@ -20,6 +22,9 @@ namespace Discord.WebSocket | |||
| private ImmutableArray<ulong> _roleIds; | |||
| internal override SocketGlobalUser GlobalUser { get; } | |||
| /// <summary> | |||
| /// Gets the guild the user is in. | |||
| /// </summary> | |||
| public SocketGuild Guild { get; } | |||
| /// <inheritdoc /> | |||
| public string Nickname { get; private set; } | |||
| @@ -50,17 +55,27 @@ namespace Discord.WebSocket | |||
| public bool IsMuted => VoiceState?.IsMuted ?? false; | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | |||
| /// <summary> | |||
| /// Returns a collection of roles that the user possesses. | |||
| /// </summary> | |||
| public IReadOnlyCollection<SocketRole> Roles | |||
| => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); | |||
| /// <summary> | |||
| /// Returns the voice channel the user is in, or <see langword="null" /> if none. | |||
| /// </summary> | |||
| public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | |||
| /// <inheritdoc /> | |||
| public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; | |||
| public SocketVoiceState? VoiceState => Guild.GetVoiceState(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 | |||
| { | |||
| get | |||