| @@ -1,28 +1,50 @@ | |||||
| | | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio 14 | # Visual Studio 14 | ||||
| VisualStudioVersion = 14.0.25123.0 | |||||
| VisualStudioVersion = 14.0.25420.1 | |||||
| MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" | |||||
| ProjectSection(SolutionItems) = preProject | |||||
| global.json = global.json | |||||
| README.md = README.md | |||||
| EndProjectSection | |||||
| EndProject | EndProject | ||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||||
| EndProject | |||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | |||||
| EndProject | |||||
| Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Discord.Net.Utils\Discord.Net.Utils.shproj", "{2B75119C-9893-4AAA-8D38-6176EEB09060}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" | |||||
| EndProject | EndProject | ||||
| Global | Global | ||||
| GlobalSection(SharedMSBuildProjectFiles) = preSolution | |||||
| src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13 | |||||
| EndGlobalSection | |||||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
| Release|Any CPU = Release|Any CPU | Release|Any CPU = Release|Any CPU | ||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
| HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(NestedProjects) = preSolution | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||||
| EndGlobalSection | |||||
| EndGlobal | EndGlobal | ||||
| @@ -1,6 +1,6 @@ | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| internal static class CDN | |||||
| public static class CDN | |||||
| { | { | ||||
| public static string GetApplicationIconUrl(ulong appId, string iconId) | public static string GetApplicationIconUrl(ulong appId, string iconId) | ||||
| => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; | => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; | ||||
| @@ -4,7 +4,6 @@ using Discord.Net; | |||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Discord.Rest; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -28,6 +27,7 @@ namespace Discord.API | |||||
| protected readonly JsonSerializer _serializer; | protected readonly JsonSerializer _serializer; | ||||
| protected readonly SemaphoreSlim _stateLock; | protected readonly SemaphoreSlim _stateLock; | ||||
| private readonly RestClientProvider _restClientProvider; | private readonly RestClientProvider _restClientProvider; | ||||
| private readonly string _userAgent; | |||||
| protected string _authToken; | protected string _authToken; | ||||
| protected bool _isDisposed; | protected bool _isDisposed; | ||||
| @@ -36,11 +36,13 @@ namespace Discord.API | |||||
| public LoginState LoginState { get; private set; } | public LoginState LoginState { get; private set; } | ||||
| public TokenType AuthTokenType { get; private set; } | public TokenType AuthTokenType { get; private set; } | ||||
| internal RequestQueue RequestQueue { get; private set; } | |||||
| public User CurrentUser { get; private set; } | |||||
| public RequestQueue RequestQueue { get; private set; } | |||||
| public DiscordRestApiClient(RestClientProvider restClientProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) | |||||
| public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, JsonSerializer serializer = null, RequestQueue requestQueue = null) | |||||
| { | { | ||||
| _restClientProvider = restClientProvider; | _restClientProvider = restClientProvider; | ||||
| _userAgent = userAgent; | |||||
| _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| RequestQueue = requestQueue; | RequestQueue = requestQueue; | ||||
| @@ -52,7 +54,7 @@ namespace Discord.API | |||||
| { | { | ||||
| _restClient = _restClientProvider(baseUrl); | _restClient = _restClientProvider(baseUrl); | ||||
| _restClient.SetHeader("accept", "*/*"); | _restClient.SetHeader("accept", "*/*"); | ||||
| _restClient.SetHeader("user-agent", DiscordRestConfig.UserAgent); | |||||
| _restClient.SetHeader("user-agent", _userAgent); | |||||
| _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); | _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); | ||||
| } | } | ||||
| internal static string GetPrefixedToken(TokenType tokenType, string token) | internal static string GetPrefixedToken(TokenType tokenType, string token) | ||||
| @@ -111,6 +113,8 @@ namespace Discord.API | |||||
| _authToken = token; | _authToken = token; | ||||
| _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); | _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); | ||||
| CurrentUser = await GetMyUserAsync(); | |||||
| LoginState = LoginState.LoggedIn; | LoginState = LoginState.LoggedIn; | ||||
| } | } | ||||
| catch (Exception) | catch (Exception) | ||||
| @@ -144,6 +148,7 @@ namespace Discord.API | |||||
| await RequestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false); | await RequestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false); | ||||
| _restClient.SetCancelToken(CancellationToken.None); | _restClient.SetCancelToken(CancellationToken.None); | ||||
| CurrentUser = null; | |||||
| LoginState = LoginState.LoggedOut; | LoginState = LoginState.LoggedOut; | ||||
| } | } | ||||
| @@ -268,8 +273,8 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.GreaterThan(args._bitrate, 0, nameof(args.Bitrate)); | |||||
| Preconditions.NotNullOrWhitespace(args._name, nameof(args.Name)); | |||||
| Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); | |||||
| Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | |||||
| return await SendAsync<Channel>("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false); | return await SendAsync<Channel>("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -283,8 +288,8 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.AtLeast(args._position, 0, nameof(args.Position)); | |||||
| Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); | |||||
| Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); | |||||
| Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||||
| return await SendAsync<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); | return await SendAsync<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -292,8 +297,8 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.AtLeast(args._position, 0, nameof(args.Position)); | |||||
| Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); | |||||
| Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); | |||||
| Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||||
| return await SendAsync<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); | return await SendAsync<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -301,10 +306,10 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.GreaterThan(args._bitrate, 0, nameof(args.Bitrate)); | |||||
| Preconditions.AtLeast(args._userLimit, 0, nameof(args.Bitrate)); | |||||
| Preconditions.AtLeast(args._position, 0, nameof(args.Position)); | |||||
| Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); | |||||
| Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); | |||||
| Preconditions.AtLeast(args.UserLimit, 0, nameof(args.Bitrate)); | |||||
| Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); | |||||
| Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||||
| return await SendAsync<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); | return await SendAsync<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -326,6 +331,132 @@ namespace Discord.API | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| //Channel Messages | |||||
| public async Task<Message> GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| try | |||||
| { | |||||
| return await SendAsync<Message>("GET", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } | |||||
| } | |||||
| public async Task<IReadOnlyCollection<Message>> GetChannelMessagesAsync(ulong channelId, GetChannelMessagesParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); | |||||
| Preconditions.AtMost(args.Limit, DiscordConfig.MaxMessagesPerBatch, nameof(args.Limit)); | |||||
| int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxMessagesPerBatch); | |||||
| ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; | |||||
| string relativeDir; | |||||
| switch (args.RelativeDirection.GetValueOrDefault(Direction.Before)) | |||||
| { | |||||
| case Direction.Before: | |||||
| default: | |||||
| relativeDir = "before"; | |||||
| break; | |||||
| case Direction.After: | |||||
| relativeDir = "after"; | |||||
| break; | |||||
| case Direction.Around: | |||||
| relativeDir = "around"; | |||||
| break; | |||||
| } | |||||
| string endpoint; | |||||
| if (relativeId != null) | |||||
| endpoint = $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; | |||||
| else | |||||
| endpoint = $"channels/{channelId}/messages?limit={limit}"; | |||||
| return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||||
| if (args.Content.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| return await SendAsync<Message>("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| if (args.Content.GetValueOrDefault(null) == null) | |||||
| args.Content = ""; | |||||
| else if (args.Content.IsSpecified) | |||||
| { | |||||
| if (args.Content.Value == null) | |||||
| args.Content = ""; | |||||
| if (args.Content.Value?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| } | |||||
| return await SendMultipartAsync<Message>("POST", $"channels/{channelId}/messages", args.ToDictionary(), GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task DeleteMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.NotNull(args.MessageIds, nameof(args.MessageIds)); | |||||
| Preconditions.AtMost(args.MessageIds.Length, 100, nameof(args.MessageIds.Length)); | |||||
| switch (args.MessageIds.Length) | |||||
| { | |||||
| case 0: | |||||
| return; | |||||
| case 1: | |||||
| await DeleteMessageAsync(channelId, args.MessageIds[0]).ConfigureAwait(false); | |||||
| break; | |||||
| default: | |||||
| await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); | |||||
| break; | |||||
| } | |||||
| } | |||||
| public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| if (args.Content.IsSpecified) | |||||
| { | |||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||||
| if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| } | |||||
| return await SendAsync<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| await SendAsync("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| await SendAsync("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Channel Permissions | //Channel Permissions | ||||
| public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null) | public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null) | ||||
| @@ -399,7 +530,7 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | ||||
| Preconditions.NotNullOrWhitespace(args.Region, nameof(args.Region)); | |||||
| Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); | |||||
| return await SendAsync<Guild>("POST", "guilds", args, options: options).ConfigureAwait(false); | return await SendAsync<Guild>("POST", "guilds", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -419,11 +550,11 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.NotEqual(args._afkChannelId, 0, nameof(args.AFKChannelId)); | |||||
| Preconditions.AtLeast(args._afkTimeout, 0, nameof(args.AFKTimeout)); | |||||
| Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); | |||||
| Preconditions.GreaterThan(args._ownerId, 0, nameof(args.OwnerId)); | |||||
| Preconditions.NotNull(args._region, nameof(args.Region)); | |||||
| Preconditions.NotEqual(args.AfkChannelId, 0, nameof(args.AfkChannelId)); | |||||
| Preconditions.AtLeast(args.AfkTimeout, 0, nameof(args.AfkTimeout)); | |||||
| Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||||
| Preconditions.GreaterThan(args.OwnerId, 0, nameof(args.OwnerId)); | |||||
| Preconditions.NotNull(args.RegionId, nameof(args.RegionId)); | |||||
| return await SendAsync<Guild>("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false); | return await SendAsync<Guild>("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -456,7 +587,7 @@ namespace Discord.API | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotEqual(userId, 0, nameof(userId)); | Preconditions.NotEqual(userId, 0, nameof(userId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.AtLeast(args._deleteMessageDays, 0, nameof(args.DeleteMessageDays)); | |||||
| Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays)); | |||||
| await SendAsync("PUT", $"guilds/{guildId}/bans/{userId}", args, options: options).ConfigureAwait(false); | await SendAsync("PUT", $"guilds/{guildId}/bans/{userId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -514,8 +645,8 @@ namespace Discord.API | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); | Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.AtLeast(args._expireBehavior, 0, nameof(args.ExpireBehavior)); | |||||
| Preconditions.AtLeast(args._expireGracePeriod, 0, nameof(args.ExpireGracePeriod)); | |||||
| Preconditions.AtLeast(args.ExpireBehavior, 0, nameof(args.ExpireBehavior)); | |||||
| Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); | |||||
| return await SendAsync<Integration>("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false); | return await SendAsync<Integration>("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -552,18 +683,18 @@ namespace Discord.API | |||||
| return await SendAsync<IReadOnlyCollection<InviteMetadata>>("GET", $"guilds/{guildId}/invites", options: options).ConfigureAwait(false); | return await SendAsync<IReadOnlyCollection<InviteMetadata>>("GET", $"guilds/{guildId}/invites", options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task<InviteMetadata[]> GetChannelInvitesAsync(ulong channelId, RequestOptions options = null) | |||||
| public async Task<IReadOnlyCollection<InviteMetadata>> GetChannelInvitesAsync(ulong channelId, RequestOptions options = null) | |||||
| { | { | ||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
| return await SendAsync<InviteMetadata[]>("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false); | |||||
| return await SendAsync<IReadOnlyCollection<InviteMetadata>>("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false); | |||||
| } | } | ||||
| public async Task<InviteMetadata> CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null) | public async Task<InviteMetadata> CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null) | ||||
| { | { | ||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.AtLeast(args._maxAge, 0, nameof(args.MaxAge)); | |||||
| Preconditions.AtLeast(args._maxUses, 0, nameof(args.MaxUses)); | |||||
| Preconditions.AtLeast(args.MaxAge, 0, nameof(args.MaxAge)); | |||||
| Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses)); | |||||
| return await SendAsync<InviteMetadata>("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false); | return await SendAsync<InviteMetadata>("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -596,42 +727,15 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.GreaterThan(args._limit, 0, nameof(args.Limit)); | |||||
| Preconditions.GreaterThan(args._afterUserId, 0, nameof(args.AfterUserId)); | |||||
| int limit = args._limit.GetValueOrDefault(int.MaxValue); | |||||
| ulong afterUserId = args._afterUserId.GetValueOrDefault(0); | |||||
| Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); | |||||
| Preconditions.AtMost(args.Limit, DiscordConfig.MaxUsersPerBatch, nameof(args.Limit)); | |||||
| Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); | |||||
| List<GuildMember[]> result; | |||||
| if (args._limit.IsSpecified) | |||||
| result = new List<GuildMember[]>((limit + DiscordConfig.MaxUsersPerBatch - 1) / DiscordConfig.MaxUsersPerBatch); | |||||
| else | |||||
| result = new List<GuildMember[]>(); | |||||
| while (true) | |||||
| { | |||||
| int runLimit = (limit >= DiscordConfig.MaxUsersPerBatch) ? DiscordConfig.MaxUsersPerBatch : limit; | |||||
| string endpoint = $"guilds/{guildId}/members?limit={runLimit}&after={afterUserId}"; | |||||
| var models = await SendAsync<GuildMember[]>("GET", endpoint, options: options).ConfigureAwait(false); | |||||
| //Was this an empty batch? | |||||
| if (models.Length == 0) break; | |||||
| result.Add(models); | |||||
| limit -= DiscordConfig.MaxUsersPerBatch; | |||||
| afterUserId = models[models.Length - 1].User.Id; | |||||
| //Was this an incomplete (the last) batch? | |||||
| if (models.Length != DiscordConfig.MaxUsersPerBatch) break; | |||||
| } | |||||
| if (result.Count > 1) | |||||
| return result.SelectMany(x => x).ToImmutableArray(); | |||||
| else if (result.Count == 1) | |||||
| return result[0]; | |||||
| else | |||||
| return ImmutableArray.Create<GuildMember>(); | |||||
| int limit = args.Limit.GetValueOrDefault(int.MaxValue); | |||||
| ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); | |||||
| string endpoint = $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; | |||||
| return await SendAsync<IReadOnlyCollection<GuildMember>>("GET", endpoint, options: options).ConfigureAwait(false); | |||||
| } | } | ||||
| public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) | public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -646,7 +750,18 @@ namespace Discord.API | |||||
| Preconditions.NotEqual(userId, 0, nameof(userId)); | Preconditions.NotEqual(userId, 0, nameof(userId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| await SendAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId, options: options).ConfigureAwait(false); | |||||
| bool isCurrentUser = userId == CurrentUser.Id; | |||||
| if (isCurrentUser && args.Nickname.IsSpecified) | |||||
| { | |||||
| var nickArgs = new ModifyCurrentUserNickParams(args.Nickname.Value ?? ""); | |||||
| await ModifyMyNickAsync(guildId, nickArgs).ConfigureAwait(false); | |||||
| args.Nickname = Optional.Create<string>(); //Remove | |||||
| } | |||||
| if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) | |||||
| { | |||||
| await SendAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId, options: options).ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| //Guild Roles | //Guild Roles | ||||
| @@ -674,9 +789,9 @@ namespace Discord.API | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotEqual(roleId, 0, nameof(roleId)); | Preconditions.NotEqual(roleId, 0, nameof(roleId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.AtLeast(args._color, 0, nameof(args.Color)); | |||||
| Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); | |||||
| Preconditions.AtLeast(args._position, 0, nameof(args.Position)); | |||||
| Preconditions.AtLeast(args.Color, 0, nameof(args.Color)); | |||||
| Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||||
| Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); | |||||
| return await SendAsync<Role>("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false); | return await SendAsync<Role>("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -697,266 +812,6 @@ namespace Discord.API | |||||
| } | } | ||||
| } | } | ||||
| //Messages | |||||
| public async Task<Message> GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| try | |||||
| { | |||||
| return await SendAsync<Message>("GET", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } | |||||
| } | |||||
| public async Task<IReadOnlyCollection<Message>> GetChannelMessagesAsync(ulong channelId, GetChannelMessagesParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); | |||||
| int limit = args.Limit; | |||||
| ulong? relativeId = args._relativeMessageId.IsSpecified ? args._relativeMessageId.Value : (ulong?)null; | |||||
| string relativeDir; | |||||
| switch (args.RelativeDirection) | |||||
| { | |||||
| case Direction.Before: | |||||
| default: | |||||
| relativeDir = "before"; | |||||
| break; | |||||
| case Direction.After: | |||||
| relativeDir = "after"; | |||||
| break; | |||||
| case Direction.Around: | |||||
| relativeDir = "around"; | |||||
| break; | |||||
| } | |||||
| int runs = (limit + DiscordConfig.MaxMessagesPerBatch - 1) / DiscordConfig.MaxMessagesPerBatch; | |||||
| int lastRunCount = limit - (runs - 1) * DiscordConfig.MaxMessagesPerBatch; | |||||
| var result = new API.Message[runs][]; | |||||
| int i = 0; | |||||
| for (; i < runs; i++) | |||||
| { | |||||
| int runCount = i == (runs - 1) ? lastRunCount : DiscordConfig.MaxMessagesPerBatch; | |||||
| string endpoint; | |||||
| if (relativeId != null) | |||||
| endpoint = $"channels/{channelId}/messages?limit={runCount}&{relativeDir}={relativeId}"; | |||||
| else | |||||
| endpoint = $"channels/{channelId}/messages?limit={runCount}"; | |||||
| var models = await SendAsync<Message[]>("GET", endpoint, options: options).ConfigureAwait(false); | |||||
| //Was this an empty batch? | |||||
| if (models.Length == 0) break; | |||||
| //We can't assume these messages to be sorted by id (fails in rare cases), lets search for the highest/lowest id ourselves | |||||
| switch (args.RelativeDirection) | |||||
| { | |||||
| case Direction.Before: | |||||
| case Direction.Around: | |||||
| default: | |||||
| result[i] = models; | |||||
| relativeId = ulong.MaxValue; | |||||
| //Lowest id *should* be the last one | |||||
| for (int j = models.Length - 1; j >= 0; j--) | |||||
| { | |||||
| if (models[j].Id < relativeId.Value) | |||||
| relativeId = models[j].Id; | |||||
| } | |||||
| break; | |||||
| case Direction.After: | |||||
| result[runs - i - 1] = models; | |||||
| relativeId = ulong.MinValue; | |||||
| //Highest id *should* be the first one | |||||
| for (int j = 0; j < models.Length; j++) | |||||
| { | |||||
| if (models[j].Id > relativeId.Value) | |||||
| relativeId = models[j].Id; | |||||
| } | |||||
| break; | |||||
| } | |||||
| //Was this an incomplete (the last) batch? | |||||
| if (models.Length != DiscordConfig.MaxMessagesPerBatch) { i++; break; } | |||||
| } | |||||
| if (i > 1) | |||||
| { | |||||
| switch (args.RelativeDirection) | |||||
| { | |||||
| case Direction.Before: | |||||
| case Direction.Around: | |||||
| default: | |||||
| return result.Take(i).SelectMany(x => x).ToImmutableArray(); | |||||
| case Direction.After: | |||||
| return result.Skip(runs - i).Take(i).SelectMany(x => x).ToImmutableArray(); | |||||
| } | |||||
| } | |||||
| else if (i == 1) | |||||
| { | |||||
| switch (args.RelativeDirection) | |||||
| { | |||||
| case Direction.Before: | |||||
| case Direction.Around: | |||||
| default: | |||||
| return result[0]; | |||||
| case Direction.After: | |||||
| return result[runs - 1]; | |||||
| } | |||||
| } | |||||
| else | |||||
| return ImmutableArray.Create<Message>(); | |||||
| } | |||||
| public Task<Message> CreateMessageAsync(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
| return CreateMessageInternalAsync(guildId, channelId, args); | |||||
| } | |||||
| public Task<Message> CreateDMMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| return CreateMessageInternalAsync(0, channelId, args); | |||||
| } | |||||
| private async Task<Message> CreateMessageInternalAsync(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.NotNullOrEmpty(args._content, nameof(args.Content)); | |||||
| if (args._content.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| if (guildId != 0) | |||||
| return await SendAsync<Message>("POST", $"channels/{channelId}/messages", args, GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false); | |||||
| else | |||||
| return await SendAsync<Message>("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public Task<Message> UploadFileAsync(ulong guildId, ulong channelId, UploadFileParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
| return UploadFileInternalAsync(guildId, channelId, args); | |||||
| } | |||||
| public Task<Message> UploadDMFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) | |||||
| { | |||||
| return UploadFileInternalAsync(0, channelId, args); | |||||
| } | |||||
| private async Task<Message> UploadFileInternalAsync(ulong guildId, ulong channelId, UploadFileParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| if (args._content.GetValueOrDefault(null) == null) | |||||
| args._content = ""; | |||||
| else if (args._content.IsSpecified) | |||||
| { | |||||
| if (args._content.Value == null) | |||||
| args._content = ""; | |||||
| if (args._content.Value?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| } | |||||
| if (guildId != 0) | |||||
| return await SendMultipartAsync<Message>("POST", $"channels/{channelId}/messages", args.ToDictionary(), GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false); | |||||
| else | |||||
| return await SendMultipartAsync<Message>("POST", $"channels/{channelId}/messages", args.ToDictionary(), GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public Task DeleteMessageAsync(ulong guildId, ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
| return DeleteMessageInternalAsync(guildId, channelId, messageId); | |||||
| } | |||||
| public Task DeleteDMMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| return DeleteMessageInternalAsync(0, channelId, messageId); | |||||
| } | |||||
| private async Task DeleteMessageInternalAsync(ulong guildId, ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| if (guildId != 0) | |||||
| await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", GuildBucket.DeleteMessage, guildId, options: options).ConfigureAwait(false); | |||||
| else | |||||
| await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); | |||||
| } | |||||
| public Task DeleteMessagesAsync(ulong guildId, ulong channelId, DeleteMessagesParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
| return DeleteMessagesInternalAsync(guildId, channelId, args); | |||||
| } | |||||
| public Task DeleteDMMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) | |||||
| { | |||||
| return DeleteMessagesInternalAsync(0, channelId, args); | |||||
| } | |||||
| private async Task DeleteMessagesInternalAsync(ulong guildId, ulong channelId, DeleteMessagesParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| var messageIds = args._messages; | |||||
| Preconditions.NotNull(args._messages, nameof(args.MessageIds)); | |||||
| Preconditions.AtMost(messageIds.Length, 100, nameof(messageIds.Length)); | |||||
| switch (messageIds.Length) | |||||
| { | |||||
| case 0: | |||||
| return; | |||||
| case 1: | |||||
| await DeleteMessageInternalAsync(guildId, channelId, messageIds[0]).ConfigureAwait(false); | |||||
| break; | |||||
| default: | |||||
| if (guildId != 0) | |||||
| await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, GuildBucket.DeleteMessages, guildId, options: options).ConfigureAwait(false); | |||||
| else | |||||
| await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); | |||||
| break; | |||||
| } | |||||
| } | |||||
| public Task<Message> ModifyMessageAsync(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
| return ModifyMessageInternalAsync(guildId, channelId, messageId, args); | |||||
| } | |||||
| public Task<Message> ModifyDMMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| return ModifyMessageInternalAsync(0, channelId, messageId, args); | |||||
| } | |||||
| private async Task<Message> ModifyMessageInternalAsync(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| if (args._content.IsSpecified) | |||||
| { | |||||
| Preconditions.NotNullOrEmpty(args._content, nameof(args.Content)); | |||||
| if (args._content.Value.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| } | |||||
| if (guildId != 0) | |||||
| return await SendAsync<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args, GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false); | |||||
| else | |||||
| return await SendAsync<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||||
| await SendAsync("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| await SendAsync("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Users | //Users | ||||
| public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null) | public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -1012,7 +867,7 @@ namespace Discord.API | |||||
| public async Task<User> ModifySelfAsync(ModifyCurrentUserParams args, RequestOptions options = null) | public async Task<User> ModifySelfAsync(ModifyCurrentUserParams args, RequestOptions options = null) | ||||
| { | { | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.NotNullOrEmpty(args._username, nameof(args.Username)); | |||||
| Preconditions.NotNullOrEmpty(args.Username, nameof(args.Username)); | |||||
| return await SendAsync<User>("PATCH", "users/@me", args, options: options).ConfigureAwait(false); | return await SendAsync<User>("PATCH", "users/@me", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -1026,7 +881,7 @@ namespace Discord.API | |||||
| public async Task<Channel> CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) | public async Task<Channel> CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) | ||||
| { | { | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| Preconditions.GreaterThan(args._recipientId, 0, nameof(args.Recipient)); | |||||
| Preconditions.GreaterThan(args.RecipientId, 0, nameof(args.RecipientId)); | |||||
| return await SendAsync<Channel>("POST", $"users/@me/channels", args, options: options).ConfigureAwait(false); | return await SendAsync<Channel>("POST", $"users/@me/channels", args, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| internal struct Image | |||||
| public struct Image | |||||
| { | { | ||||
| public Stream Stream { get; } | public Stream Stream { get; } | ||||
| public string Hash { get; } | public string Hash { get; } | ||||
| @@ -0,0 +1,16 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class CreateChannelInviteParams | |||||
| { | |||||
| [JsonProperty("max_age")] | |||||
| public Optional<int> MaxAge { get; set; } | |||||
| [JsonProperty("max_uses")] | |||||
| public Optional<int> MaxUses { get; set; } | |||||
| [JsonProperty("temporary")] | |||||
| public Optional<bool> IsTemporary { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,8 +7,11 @@ namespace Discord.API.Rest | |||||
| public class CreateDMChannelParams | public class CreateDMChannelParams | ||||
| { | { | ||||
| [JsonProperty("recipient_id")] | [JsonProperty("recipient_id")] | ||||
| internal ulong _recipientId { get; set; } | |||||
| public ulong RecipientId { set { _recipientId = value; } } | |||||
| public IUser Recipient { set { _recipientId = value.Id; } } | |||||
| public ulong RecipientId { get; } | |||||
| public CreateDMChannelParams(ulong recipientId) | |||||
| { | |||||
| RecipientId = recipientId; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,7 +7,6 @@ namespace Discord.API.Rest | |||||
| public class CreateGuildBanParams | public class CreateGuildBanParams | ||||
| { | { | ||||
| [JsonProperty("delete-message-days")] | [JsonProperty("delete-message-days")] | ||||
| internal Optional<int> _deleteMessageDays { get; set; } | |||||
| public int DeleteMessageDays { set { _deleteMessageDays = value; } } | |||||
| public Optional<int> DeleteMessageDays { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,23 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class CreateGuildChannelParams | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; } | |||||
| [JsonProperty("type")] | |||||
| public ChannelType Type { get; } | |||||
| [JsonProperty("bitrate")] | |||||
| public Optional<int> Bitrate { get; set; } | |||||
| public CreateGuildChannelParams(string name, ChannelType type) | |||||
| { | |||||
| Name = name; | |||||
| Type = type; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -7,9 +7,14 @@ namespace Discord.API.Rest | |||||
| public class CreateGuildIntegrationParams | public class CreateGuildIntegrationParams | ||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { internal get; set; } | |||||
| public ulong Id { get; } | |||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { internal get; set; } | |||||
| public string Type { get; } | |||||
| public CreateGuildIntegrationParams(ulong id, string type) | |||||
| { | |||||
| Id = id; | |||||
| Type = type; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,5 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.IO; | |||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| { | { | ||||
| @@ -8,13 +7,17 @@ namespace Discord.API.Rest | |||||
| public class CreateGuildParams | public class CreateGuildParams | ||||
| { | { | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { internal get; set; } | |||||
| public string Name { get; } | |||||
| [JsonProperty("region")] | [JsonProperty("region")] | ||||
| public string Region { internal get; set; } | |||||
| public string RegionId { get; } | |||||
| [JsonProperty("icon")] | [JsonProperty("icon")] | ||||
| internal Optional<Image?> _icon { get; set; } | |||||
| public Stream Icon { set { _icon = value != null ? new Image(value) : (Image?)null; } } | |||||
| public Optional<Image?> Icon { get; set; } | |||||
| public CreateGuildParams(string name, string regionId) | |||||
| { | |||||
| Name = name; | |||||
| RegionId = regionId; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,22 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class CreateMessageParams | |||||
| { | |||||
| [JsonProperty("content")] | |||||
| public string Content { get; } | |||||
| [JsonProperty("nonce")] | |||||
| public Optional<string> Nonce { get; set; } | |||||
| [JsonProperty("tts")] | |||||
| public Optional<bool> IsTTS { get; set; } | |||||
| public CreateMessageParams(string content) | |||||
| { | |||||
| Content = content; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class DeleteMessagesParams | |||||
| { | |||||
| [JsonProperty("messages")] | |||||
| public ulong[] MessageIds { get; } | |||||
| public DeleteMessagesParams(ulong[] messageIds) | |||||
| { | |||||
| MessageIds = messageIds; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class GetChannelMessagesParams | |||||
| { | |||||
| public Optional<int> Limit { get; set; } | |||||
| public Optional<Direction> RelativeDirection { get; set; } | |||||
| public Optional<ulong> RelativeMessageId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class GetGuildMembersParams | |||||
| { | |||||
| public Optional<int> Limit { get; set; } | |||||
| public Optional<ulong> AfterUserId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,6 +7,11 @@ namespace Discord.API.Rest | |||||
| public class GuildPruneParams | public class GuildPruneParams | ||||
| { | { | ||||
| [JsonProperty("days")] | [JsonProperty("days")] | ||||
| public int Days { internal get; set; } | |||||
| public int Days { get; } | |||||
| public GuildPruneParams(int days) | |||||
| { | |||||
| Days = days; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,10 +7,17 @@ namespace Discord.API.Rest | |||||
| public class ModifyChannelPermissionsParams | public class ModifyChannelPermissionsParams | ||||
| { | { | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { internal get; set; } | |||||
| public string Type { get; } | |||||
| [JsonProperty("allow")] | [JsonProperty("allow")] | ||||
| public ulong Allow { internal get; set; } | |||||
| public ulong Allow { get; } | |||||
| [JsonProperty("deny")] | [JsonProperty("deny")] | ||||
| public ulong Deny { internal get; set; } | |||||
| public ulong Deny { get; } | |||||
| public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) | |||||
| { | |||||
| Type = type; | |||||
| Allow = allow; | |||||
| Deny = deny; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,6 +7,11 @@ namespace Discord.API.Rest | |||||
| public class ModifyCurrentUserNickParams | public class ModifyCurrentUserNickParams | ||||
| { | { | ||||
| [JsonProperty("nick")] | [JsonProperty("nick")] | ||||
| public string Nickname { internal get; set; } | |||||
| public string Nickname { get; } | |||||
| public ModifyCurrentUserNickParams(string nickname) | |||||
| { | |||||
| Nickname = nickname; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,5 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.IO; | |||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| { | { | ||||
| @@ -8,11 +7,8 @@ namespace Discord.API.Rest | |||||
| public class ModifyCurrentUserParams | public class ModifyCurrentUserParams | ||||
| { | { | ||||
| [JsonProperty("username")] | [JsonProperty("username")] | ||||
| internal Optional<string> _username { get; set; } | |||||
| public string Username { set { _username = value; } } | |||||
| public Optional<string> Username { get; set; } | |||||
| [JsonProperty("avatar")] | [JsonProperty("avatar")] | ||||
| internal Optional<Image> _avatar { get; set; } | |||||
| public Stream Avatar { set { _avatar = new Image(value); } } | |||||
| public Optional<Image> Avatar { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,11 +7,8 @@ namespace Discord.API.Rest | |||||
| public class ModifyGuildChannelParams | public class ModifyGuildChannelParams | ||||
| { | { | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| internal Optional<string> _name { get; set; } | |||||
| public string Name { set { _name = value; } } | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| internal Optional<int> _position { get; set; } | |||||
| public int Position { set { _position = value; } } | |||||
| public Optional<int> Position { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,9 +7,14 @@ namespace Discord.API.Rest | |||||
| public class ModifyGuildChannelsParams | public class ModifyGuildChannelsParams | ||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { internal get; set; } | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| public int Position { internal get; set; } | |||||
| public int Position { get; set; } | |||||
| public ModifyGuildChannelsParams(ulong id, int position) | |||||
| { | |||||
| Id = id; | |||||
| Position = position; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,14 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildEmbedParams | |||||
| { | |||||
| [JsonProperty("enabled")] | |||||
| public Optional<bool> Enabled { get; set; } | |||||
| [JsonProperty("channel")] | |||||
| public Optional<ulong?> ChannelId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildIntegrationParams | |||||
| { | |||||
| [JsonProperty("expire_behavior")] | |||||
| public Optional<int> ExpireBehavior { get; set; } | |||||
| [JsonProperty("expire_grace_period")] | |||||
| public Optional<int> ExpireGracePeriod { get; set; } | |||||
| [JsonProperty("enable_emoticons")] | |||||
| public Optional<bool> EnableEmoticons { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildMemberParams | |||||
| { | |||||
| [JsonProperty("mute")] | |||||
| public Optional<bool> Mute { get; set; } | |||||
| [JsonProperty("deaf")] | |||||
| public Optional<bool> Deaf { get; set; } | |||||
| [JsonProperty("nick")] | |||||
| public Optional<string> Nickname { get; set; } | |||||
| [JsonProperty("roles")] | |||||
| public Optional<ulong[]> RoleIds { get; set; } | |||||
| [JsonProperty("channel_id")] | |||||
| public Optional<ulong> ChannelId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildParams | |||||
| { | |||||
| [JsonProperty("username")] | |||||
| public Optional<string> Username { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("region")] | |||||
| public Optional<string> RegionId { get; set; } | |||||
| [JsonProperty("verification_level")] | |||||
| public Optional<VerificationLevel> VerificationLevel { get; set; } | |||||
| [JsonProperty("default_message_notifications")] | |||||
| public Optional<DefaultMessageNotifications> DefaultMessageNotifications { get; set; } | |||||
| [JsonProperty("afk_timeout")] | |||||
| public Optional<int> AfkTimeout { get; set; } | |||||
| [JsonProperty("icon")] | |||||
| public Optional<Image?> Icon { get; set; } | |||||
| [JsonProperty("splash")] | |||||
| public Optional<Image?> Splash { get; set; } | |||||
| [JsonProperty("afk_channel_id")] | |||||
| public Optional<ulong?> AfkChannelId { get; set; } | |||||
| [JsonProperty("owner_id")] | |||||
| public Optional<ulong> OwnerId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildRoleParams | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("permissions")] | |||||
| public Optional<ulong> Permissions { get; set; } | |||||
| [JsonProperty("position")] | |||||
| public Optional<int> Position { get; set; } | |||||
| [JsonProperty("color")] | |||||
| public Optional<uint> Color { get; set; } | |||||
| [JsonProperty("hoist")] | |||||
| public Optional<bool> Hoist { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,6 +7,11 @@ namespace Discord.API.Rest | |||||
| public class ModifyGuildRolesParams : ModifyGuildRoleParams | public class ModifyGuildRolesParams : ModifyGuildRoleParams | ||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { internal get; set; } | |||||
| public ulong Id { get; } | |||||
| public ModifyGuildRolesParams(ulong id) | |||||
| { | |||||
| Id = id; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,7 +7,6 @@ namespace Discord.API.Rest | |||||
| public class ModifyMessageParams | public class ModifyMessageParams | ||||
| { | { | ||||
| [JsonProperty("content")] | [JsonProperty("content")] | ||||
| internal Optional<string> _content { get; set; } | |||||
| public string Content { set { _content = value; } } | |||||
| public Optional<string> Content { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,9 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class ModifyPresenceParams | |||||
| { | |||||
| public Optional<UserStatus> Status { get; set; } | |||||
| public Optional<Game> Game { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,7 +7,6 @@ namespace Discord.API.Rest | |||||
| public class ModifyTextChannelParams : ModifyGuildChannelParams | public class ModifyTextChannelParams : ModifyGuildChannelParams | ||||
| { | { | ||||
| [JsonProperty("topic")] | [JsonProperty("topic")] | ||||
| internal Optional<string> _topic { get; set; } | |||||
| public string Topic { set { _topic = value; } } | |||||
| public Optional<string> Topic { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,11 +7,8 @@ namespace Discord.API.Rest | |||||
| public class ModifyVoiceChannelParams : ModifyGuildChannelParams | public class ModifyVoiceChannelParams : ModifyGuildChannelParams | ||||
| { | { | ||||
| [JsonProperty("bitrate")] | [JsonProperty("bitrate")] | ||||
| internal Optional<int> _bitrate { get; set; } | |||||
| public int Bitrate { set { _bitrate = value; } } | |||||
| public Optional<int> Bitrate { get; set; } | |||||
| [JsonProperty("user_limit")] | [JsonProperty("user_limit")] | ||||
| internal Optional<int> _userLimit { get; set; } | |||||
| public int UserLimit { set { _userLimit = value; } } | |||||
| public Optional<int> UserLimit { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,35 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Discord.Net.Rest; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class UploadFileParams | |||||
| { | |||||
| public Stream File { get; } | |||||
| public Optional<string> Filename { get; set; } | |||||
| public Optional<string> Content { get; set; } | |||||
| public Optional<string> Nonce { get; set; } | |||||
| public Optional<bool> IsTTS { get; set; } | |||||
| public UploadFileParams(Stream file) | |||||
| { | |||||
| File = file; | |||||
| } | |||||
| public IReadOnlyDictionary<string, object> ToDictionary() | |||||
| { | |||||
| var d = new Dictionary<string, object>(); | |||||
| d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); | |||||
| if (Content.IsSpecified) | |||||
| d["content"] = Content.Value; | |||||
| if (IsTTS.IsSpecified) | |||||
| d["tts"] = IsTTS.Value.ToString(); | |||||
| if (Nonce.IsSpecified) | |||||
| d["nonce"] = Nonce.Value; | |||||
| return d; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.IO; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| @@ -8,8 +9,7 @@ namespace Discord.Audio | |||||
| event Func<Task> Connected; | event Func<Task> Connected; | ||||
| event Func<Exception, Task> Disconnected; | event Func<Exception, Task> Disconnected; | ||||
| event Func<int, int, Task> LatencyUpdated; | event Func<int, int, Task> LatencyUpdated; | ||||
| DiscordVoiceAPIClient ApiClient { get; } | |||||
| /// <summary> Gets the current connection state of this client. </summary> | /// <summary> Gets the current connection state of this client. </summary> | ||||
| ConnectionState ConnectionState { get; } | ConnectionState ConnectionState { get; } | ||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | ||||
| @@ -17,7 +17,7 @@ namespace Discord.Audio | |||||
| Task DisconnectAsync(); | Task DisconnectAsync(); | ||||
| RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||||
| OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | |||||
| Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||||
| Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,19 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <PropertyGroup> | |||||
| <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||||
| <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||||
| </PropertyGroup> | |||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||||
| <PropertyGroup Label="Globals"> | |||||
| <ProjectGuid>91e9e7bd-75c9-4e98-84aa-2c271922e5c2</ProjectGuid> | |||||
| <RootNamespace>Discord</RootNamespace> | |||||
| <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||||
| <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||||
| <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | |||||
| </PropertyGroup> | |||||
| <PropertyGroup> | |||||
| <SchemaVersion>2.0</SchemaVersion> | |||||
| </PropertyGroup> | |||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||||
| </Project> | |||||
| @@ -3,11 +3,15 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public interface IChannel : ISnowflakeEntity, IUpdateable | |||||
| public interface IChannel : ISnowflakeEntity | |||||
| { | { | ||||
| IReadOnlyCollection<IUser> CachedUsers { get; } | |||||
| /// <summary> Gets a collection of all users in this channel. </summary> | /// <summary> Gets a collection of all users in this channel. </summary> | ||||
| Task<IReadOnlyCollection<IUser>> GetUsersAsync(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(); | |||||
| /// <summary> Gets a user in this channel with the provided id.</summary> | /// <summary> Gets a user in this channel with the provided id.</summary> | ||||
| Task<IUser> GetUserAsync(ulong id); | Task<IUser> GetUserAsync(ulong id); | ||||
| IUser GetCachedUser(ulong id); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,16 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IGroupChannel : IMessageChannel, IPrivateChannel | |||||
| { | |||||
| ///// <summary> Adds a user to this group. </summary> | |||||
| //Task AddUserAsync(IUser user); | |||||
| //new IReadOnlyCollection<IGroupUser> CachedUsers { get; } | |||||
| /// <summary> Leaves this group. </summary> | |||||
| Task LeaveAsync(); | |||||
| } | |||||
| } | |||||
| @@ -12,8 +12,9 @@ namespace Discord | |||||
| /// <summary> Gets the position of this channel in the guild's channel list, relative to others of the same type. </summary> | /// <summary> Gets the position of this channel in the guild's channel list, relative to others of the same type. </summary> | ||||
| int Position { get; } | int Position { get; } | ||||
| /// <summary> Gets the guild this channel is a member of. </summary> | |||||
| IGuild Guild { get; } | |||||
| /// <summary> Gets the id of the guild this channel is a member of. </summary> | |||||
| ulong GuildId { get; } | |||||
| new IReadOnlyCollection<IGuildUser> CachedUsers { get; } | |||||
| /// <summary> Creates a new invite to this channel. </summary> | /// <summary> Creates a new invite to this channel. </summary> | ||||
| /// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param> | /// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param> | ||||
| @@ -43,8 +44,9 @@ namespace Discord | |||||
| Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions); | Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions); | ||||
| /// <summary> Gets a collection of all users in this channel. </summary> | /// <summary> Gets a collection of all users in this channel. </summary> | ||||
| new Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | |||||
| new IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | |||||
| /// <summary> Gets a user in this channel with the provided id.</summary> | /// <summary> Gets a user in this channel with the provided id.</summary> | ||||
| new Task<IGuildUser> GetUserAsync(ulong id); | new Task<IGuildUser> GetUserAsync(ulong id); | ||||
| new IGuildUser GetCachedUser(ulong id); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System.Collections.Generic; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | using System.IO; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -15,20 +16,21 @@ namespace Discord | |||||
| Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false); | Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false); | ||||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | /// <summary> Sends a file to this text channel, with an optional caption. </summary> | ||||
| Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | ||||
| /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | ||||
| Task<IMessage> GetMessageAsync(ulong id); | Task<IMessage> GetMessageAsync(ulong id); | ||||
| /// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary> | /// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary> | ||||
| IMessage GetCachedMessage(ulong id); | IMessage GetCachedMessage(ulong id); | ||||
| /// <summary> Gets the last N messages from this message channel. </summary> | /// <summary> Gets the last N messages from this message channel. </summary> | ||||
| Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| /// <summary> Gets a collection of messages in this channel. </summary> | /// <summary> Gets a collection of messages in this channel. </summary> | ||||
| Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, 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> | ||||
| Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(); | Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(); | ||||
| /// <summary> Bulk deletes multiple messages. </summary> | /// <summary> Bulk deletes multiple messages. </summary> | ||||
| Task DeleteMessagesAsync(IEnumerable<IMessage> messages); | Task DeleteMessagesAsync(IEnumerable<IMessage> messages); | ||||
| /// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds.</summary> | /// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds.</summary> | ||||
| Task TriggerTypingAsync(); | |||||
| IDisposable EnterTypingState(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,33 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Diagnostics; | |||||
| using Model = Discord.API.Emoji; | |||||
| namespace Discord | |||||
| { | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public struct Emoji | |||||
| { | |||||
| public ulong Id { get; } | |||||
| public string Name { get; } | |||||
| public bool IsManaged { get; } | |||||
| public bool RequireColons { get; } | |||||
| public IReadOnlyList<ulong> RoleIds { get; } | |||||
| public Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds) | |||||
| { | |||||
| Id = id; | |||||
| Name = name; | |||||
| IsManaged = isManaged; | |||||
| RequireColons = requireColons; | |||||
| RoleIds = roleIds; | |||||
| } | |||||
| public static Emoji Create(Model model) | |||||
| { | |||||
| return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); | |||||
| } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| //using Discord.Rest; | |||||
| using System.Diagnostics; | |||||
| using Model = Discord.API.Ban; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IBan | |||||
| { | |||||
| IUser User { get; } | |||||
| string Reason { get; } | |||||
| } | |||||
| } | |||||
| @@ -6,7 +6,7 @@ using Discord.Audio; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public interface IGuild : IDeletable, ISnowflakeEntity, IUpdateable | |||||
| public interface IGuild : IDeletable, ISnowflakeEntity | |||||
| { | { | ||||
| /// <summary> Gets the name of this guild. </summary> | /// <summary> Gets the name of this guild. </summary> | ||||
| string Name { get; } | string Name { get; } | ||||
| @@ -20,8 +20,12 @@ namespace Discord | |||||
| MfaLevel MfaLevel { get; } | MfaLevel MfaLevel { get; } | ||||
| /// <summary> Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. </summary> | /// <summary> Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. </summary> | ||||
| VerificationLevel VerificationLevel { get; } | VerificationLevel VerificationLevel { get; } | ||||
| /// <summary> Returns the id of this guild's icon, or null if one is not set. </summary> | |||||
| string IconId { get; } | |||||
| /// <summary> Returns the url to this guild's icon, or null if one is not set. </summary> | /// <summary> Returns the url to this guild's icon, or null if one is not set. </summary> | ||||
| string IconUrl { get; } | string IconUrl { get; } | ||||
| /// <summary> Returns the id of this guild's splash image, or null if one is not set. </summary> | |||||
| string SplashId { get; } | |||||
| /// <summary> Returns the url to this guild's splash image, or null if one is not set. </summary> | /// <summary> Returns the url to this guild's splash image, or null if one is not set. </summary> | ||||
| string SplashUrl { get; } | string SplashUrl { get; } | ||||
| /// <summary> Returns true if this guild is currently connected and ready to be used. Only applies to the WebSocket client. </summary> | /// <summary> Returns true if this guild is currently connected and ready to be used. Only applies to the WebSocket client. </summary> | ||||
| @@ -48,6 +52,7 @@ namespace Discord | |||||
| IReadOnlyCollection<string> Features { get; } | IReadOnlyCollection<string> Features { get; } | ||||
| /// <summary> Gets a collection of all roles in this guild. </summary> | /// <summary> Gets a collection of all roles in this guild. </summary> | ||||
| IReadOnlyCollection<IRole> Roles { get; } | IReadOnlyCollection<IRole> Roles { get; } | ||||
| IReadOnlyCollection<IGuildUser> CachedUsers { get; } | |||||
| /// <summary> Modifies this guild. </summary> | /// <summary> Modifies this guild. </summary> | ||||
| Task ModifyAsync(Action<ModifyGuildParams> func); | Task ModifyAsync(Action<ModifyGuildParams> func); | ||||
| @@ -61,7 +66,7 @@ namespace Discord | |||||
| Task LeaveAsync(); | Task LeaveAsync(); | ||||
| /// <summary> Gets a collection of all users banned on this guild. </summary> | /// <summary> Gets a collection of all users banned on this guild. </summary> | ||||
| Task<IReadOnlyCollection<Ban>> GetBansAsync(); | |||||
| Task<IReadOnlyCollection<IBan>> GetBansAsync(); | |||||
| /// <summary> Bans the provided user from this guild and optionally prunes their recent messages. </summary> | /// <summary> Bans the provided user from this guild and optionally prunes their recent messages. </summary> | ||||
| Task AddBanAsync(IUser user, int pruneDays = 0); | Task AddBanAsync(IUser user, int pruneDays = 0); | ||||
| /// <summary> Bans the provided user id from this guild and optionally prunes their recent messages. </summary> | /// <summary> Bans the provided user id from this guild and optionally prunes their recent messages. </summary> | ||||
| @@ -75,11 +80,15 @@ namespace Discord | |||||
| Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(); | Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(); | ||||
| /// <summary> Gets the channel in this guild with the provided id, or null if not found. </summary> | /// <summary> Gets the channel in this guild with the provided id, or null if not found. </summary> | ||||
| Task<IGuildChannel> GetChannelAsync(ulong id); | Task<IGuildChannel> GetChannelAsync(ulong id); | ||||
| IGuildChannel GetCachedChannel(ulong id); | |||||
| /// <summary> Creates a new text channel. </summary> | /// <summary> Creates a new text channel. </summary> | ||||
| Task<ITextChannel> CreateTextChannelAsync(string name); | Task<ITextChannel> CreateTextChannelAsync(string name); | ||||
| /// <summary> Creates a new voice channel. </summary> | /// <summary> Creates a new voice channel. </summary> | ||||
| Task<IVoiceChannel> CreateVoiceChannelAsync(string name); | Task<IVoiceChannel> CreateVoiceChannelAsync(string name); | ||||
| Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(); | |||||
| Task<IGuildIntegration> CreateIntegrationAsync(ulong id, string type); | |||||
| /// <summary> Gets a collection of all invites to this guild. </summary> | /// <summary> Gets a collection of all invites to this guild. </summary> | ||||
| Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(); | Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(); | ||||
| @@ -92,9 +101,10 @@ namespace Discord | |||||
| Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | ||||
| /// <summary> Gets the user in this guild with the provided id, or null if not found. </summary> | /// <summary> Gets the user in this guild with the provided id, or null if not found. </summary> | ||||
| Task<IGuildUser> GetUserAsync(ulong id); | Task<IGuildUser> GetUserAsync(ulong id); | ||||
| IGuildUser GetCachedUser(ulong id); | |||||
| /// <summary> Gets the current user for this guild. </summary> | /// <summary> Gets the current user for this guild. </summary> | ||||
| Task<IGuildUser> GetCurrentUserAsync(); | Task<IGuildUser> GetCurrentUserAsync(); | ||||
| /// <summary> Downloads all users for this guild if the current list is incomplete. Only applies to the WebSocket client. </summary> | |||||
| /// <summary> Downloads all users for this guild if the current list is incomplete. </summary> | |||||
| Task DownloadUsersAsync(); | Task DownloadUsersAsync(); | ||||
| /// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary> | /// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary> | ||||
| Task<int> PruneUsersAsync(int days = 30, bool simulate = false); | Task<int> PruneUsersAsync(int days = 30, bool simulate = false); | ||||
| @@ -2,7 +2,6 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| //TODO: Add docstrings | |||||
| public interface IGuildIntegration | public interface IGuildIntegration | ||||
| { | { | ||||
| ulong Id { get; } | ulong Id { get; } | ||||
| @@ -15,8 +14,8 @@ namespace Discord | |||||
| DateTimeOffset SyncedAt { get; } | DateTimeOffset SyncedAt { get; } | ||||
| IntegrationAccount Account { get; } | IntegrationAccount Account { get; } | ||||
| IGuild Guild { get; } | |||||
| ulong GuildId { get; } | |||||
| ulong RoleId { get; } | |||||
| IUser User { get; } | IUser User { get; } | ||||
| IRole Role { get; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,6 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public interface IApplication : ISnowflakeEntity, IUpdateable | |||||
| public interface IApplication : ISnowflakeEntity | |||||
| { | { | ||||
| string Name { get; } | string Name { get; } | ||||
| string Description { get; } | string Description { get; } | ||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IEntity<TId> | |||||
| where TId : IEquatable<TId> | |||||
| { | |||||
| /// <summary> Gets the IDiscordClient that created this object. </summary> | |||||
| IDiscordClient Discord { get; } | |||||
| /// <summary> Gets the unique identifier for this object. </summary> | |||||
| TId Id { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface ISnowflakeEntity : IEntity<ulong> | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -12,7 +12,9 @@ namespace Discord | |||||
| Name = name; | Name = name; | ||||
| Url = url; | Url = url; | ||||
| } | } | ||||
| internal EmbedProvider(Model model) | |||||
| : this(model.Name, model.Url) { } | |||||
| public static EmbedProvider Create(Model model) | |||||
| { | |||||
| return new EmbedProvider(model.Name, model.Url); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -16,14 +16,11 @@ namespace Discord | |||||
| Height = height; | Height = height; | ||||
| Width = width; | Width = width; | ||||
| } | } | ||||
| internal EmbedThumbnail(Model model) | |||||
| : this( | |||||
| model.Url, | |||||
| model.ProxyUrl, | |||||
| model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||||
| model.Width.IsSpecified ? model.Width.Value : (int?)null) | |||||
| public static EmbedThumbnail Create(Model model) | |||||
| { | { | ||||
| return new EmbedThumbnail(model.Url, model.ProxyUrl, | |||||
| model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||||
| model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||