@@ -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 | |||