| @@ -1,9 +1,11 @@ | |||||
| # Discord.Net v1.0.0-dev | # Discord.Net v1.0.0-dev | ||||
| [](https://ci.appveyor.com/project/foxbot/discord-net/branch/master) | |||||
| An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). | An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). | ||||
| Check out the [documentation](https://discordnet.readthedocs.org/en/latest/) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). | |||||
| Check out the [documentation](http://rtd.discord.foxbot.me/en/docs-dev/index.html) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). | |||||
| ##### Warning: documentation is currently outdated. | |||||
| ##### Warning: Some of the documentation is outdated. | |||||
| It's current being rewritten. Until that's done, feel free to use my [DiscordBot](https://github.com/RogueException/DiscordBot) repo for reference. | It's current being rewritten. Until that's done, feel free to use my [DiscordBot](https://github.com/RogueException/DiscordBot) repo for reference. | ||||
| ### Installation | ### Installation | ||||
| @@ -16,9 +18,7 @@ You can download Discord.Net and its extensions from NuGet: | |||||
| ### Compiling | ### Compiling | ||||
| In order to compile Discord.Net, you require at least the following: | In order to compile Discord.Net, you require at least the following: | ||||
| - [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs) | - [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs) | ||||
| - Visual Studio 2015 Update 1 or higher (available through Visual Studio) | |||||
| - [ASP.Net 5 RC1](https://get.asp.net) | |||||
| - NuGet 3.3 (available through Visual Studio) | |||||
| - [Visual Studio 2015 Update 2](https://www.visualstudio.com/en-us/news/vs2015-update2-vs.aspx) | |||||
| - [Visual Studio .Net Core Plugin](https://www.microsoft.com/net/core#windows) | |||||
| - NuGet 3.3+ (available through Visual Studio) | |||||
| ### Known Issues | |||||
| - .Net Core support is incomplete on non-Windows systems | |||||
| @@ -0,0 +1,6 @@ | |||||
| { | |||||
| "projects": [ "src", "test" ], | |||||
| "sdk": { | |||||
| "version": "1.0.0-preview1-002702" | |||||
| } | |||||
| } | |||||
| @@ -17,7 +17,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| public class DiscordAPIClient | |||||
| public class DiscordApiClient | |||||
| { | { | ||||
| internal event EventHandler<SentRequestEventArgs> SentRequest; | internal event EventHandler<SentRequestEventArgs> SentRequest; | ||||
| @@ -30,7 +30,7 @@ namespace Discord.API | |||||
| public IRestClient RestClient { get; private set; } | public IRestClient RestClient { get; private set; } | ||||
| public IRequestQueue RequestQueue { get; private set; } | public IRequestQueue RequestQueue { get; private set; } | ||||
| public DiscordAPIClient(RestClientProvider restClientProvider) | |||||
| public DiscordApiClient(RestClientProvider restClientProvider) | |||||
| { | { | ||||
| _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); | _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); | ||||
| _restClient.SetHeader("accept", "*/*"); | _restClient.SetHeader("accept", "*/*"); | ||||
| @@ -5,7 +5,7 @@ namespace Discord | |||||
| public interface IDMChannel : IMessageChannel, IUpdateable | public interface IDMChannel : IMessageChannel, IUpdateable | ||||
| { | { | ||||
| /// <summary> Gets the recipient of all messages in this channel. </summary> | /// <summary> Gets the recipient of all messages in this channel. </summary> | ||||
| IDMUser Recipient { get; } | |||||
| IUser Recipient { get; } | |||||
| /// <summary> Closes this private channel, removing it from your channel list. </summary> | /// <summary> Closes this private channel, removing it from your channel list. </summary> | ||||
| Task Close(); | Task Close(); | ||||
| @@ -1,7 +1,7 @@ | |||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using Model = Discord.API.VoiceRegion; | using Model = Discord.API.VoiceRegion; | ||||
| namespace Discord.Rest | |||||
| namespace Discord | |||||
| { | { | ||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| public class VoiceRegion : IVoiceRegion | public class VoiceRegion : IVoiceRegion | ||||
| @@ -47,13 +47,13 @@ namespace Discord | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Accept() | public async Task Accept() | ||||
| { | { | ||||
| await Discord.APIClient.AcceptInvite(Code).ConfigureAwait(false); | |||||
| await Discord.ApiClient.AcceptInvite(Code).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteInvite(Code).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteInvite(Code).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -1,7 +1,7 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using System.Collections.Generic; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -7,23 +7,37 @@ namespace Discord | |||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| public struct ChannelPermissions | public struct ChannelPermissions | ||||
| { | { | ||||
| #if CSHARP7 | |||||
| private static ChannelPermissions _allDM { get; } = new ChannelPermissions(0b000100_000000_0011111111_0000011001); | private static ChannelPermissions _allDM { get; } = new ChannelPermissions(0b000100_000000_0011111111_0000011001); | ||||
| private static ChannelPermissions _allText { get; } = new ChannelPermissions(0b000000_000000_0001110011_0000000000); | private static ChannelPermissions _allText { get; } = new ChannelPermissions(0b000000_000000_0001110011_0000000000); | ||||
| private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(0b000100_111111_0000000000_0000011001); | private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(0b000100_111111_0000000000_0000011001); | ||||
| #else | |||||
| private static ChannelPermissions _allDM { get; } = new ChannelPermissions(Convert.ToUInt64("00010000000000111111110000011001", 2)); | |||||
| private static ChannelPermissions _allText { get; } = new ChannelPermissions(Convert.ToUInt64("00000000000000011100110000000000", 2)); | |||||
| private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(Convert.ToUInt64("00010011111100000000000000011001", 2)); | |||||
| #endif | |||||
| /// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary> | /// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary> | ||||
| public static ChannelPermissions None { get; } = new ChannelPermissions(); | public static ChannelPermissions None { get; } = new ChannelPermissions(); | ||||
| /// <summary> Gets a ChannelPermissions that grants all permissions for a given channelType. </summary> | /// <summary> Gets a ChannelPermissions that grants all permissions for a given channelType. </summary> | ||||
| public static ChannelPermissions All(IChannel channel) | public static ChannelPermissions All(IChannel channel) | ||||
| { | { | ||||
| #if CSHARP7 | |||||
| switch (channel) | switch (channel) | ||||
| { | { | ||||
| case ITextChannel _: return _allText; | case ITextChannel _: return _allText; | ||||
| case IVoiceChannel _: return _allVoice; | case IVoiceChannel _: return _allVoice; | ||||
| case IGuildChannel _: return _allDM; | |||||
| case IDMChannel _: return _allDM; | |||||
| default: | default: | ||||
| throw new ArgumentException("Unknown channel type", nameof(channel)); | throw new ArgumentException("Unknown channel type", nameof(channel)); | ||||
| } | } | ||||
| #else | |||||
| if (channel is ITextChannel) return _allText; | |||||
| if (channel is IVoiceChannel) return _allVoice; | |||||
| if (channel is IDMChannel) return _allDM; | |||||
| throw new ArgumentException("Unknown channel type", nameof(channel)); | |||||
| #endif | |||||
| } | } | ||||
| /// <summary> Gets a packed value representing all the permissions in this ChannelPermissions. </summary> | /// <summary> Gets a packed value representing all the permissions in this ChannelPermissions. </summary> | ||||
| @@ -70,9 +84,9 @@ namespace Discord | |||||
| /// <summary> Creates a new ChannelPermissions with the provided packed value. </summary> | /// <summary> Creates a new ChannelPermissions with the provided packed value. </summary> | ||||
| public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } | public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } | ||||
| private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | |||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | |||||
| private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | |||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | |||||
| bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | ||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) | bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) | ||||
| { | { | ||||
| @@ -100,25 +114,25 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> Creates a new ChannelPermissions with the provided permissions. </summary> | /// <summary> Creates a new ChannelPermissions with the provided permissions. </summary> | ||||
| public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, | |||||
| bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | |||||
| bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | |||||
| public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, | |||||
| bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | |||||
| bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | |||||
| bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | ||||
| bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false) | bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false) | ||||
| : this(0, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, | |||||
| : this(0, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, | |||||
| moveMembers, useVoiceActivation, managePermissions) { } | moveMembers, useVoiceActivation, managePermissions) { } | ||||
| /// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> | /// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> | ||||
| public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | |||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | |||||
| public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | |||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | |||||
| bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | ||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) | bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) | ||||
| => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, | |||||
| => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, | |||||
| moveMembers, useVoiceActivation, managePermissions); | moveMembers, useVoiceActivation, managePermissions); | ||||
| public List<ChannelPermission> ToList() | public List<ChannelPermission> ToList() | ||||
| { | { | ||||
| var perms = new List<ChannelPermission>(); | var perms = new List<ChannelPermission>(); | ||||
| @@ -10,7 +10,11 @@ namespace Discord | |||||
| /// <summary> Gets a blank GuildPermissions that grants no permissions. </summary> | /// <summary> Gets a blank GuildPermissions that grants no permissions. </summary> | ||||
| public static readonly GuildPermissions None = new GuildPermissions(); | public static readonly GuildPermissions None = new GuildPermissions(); | ||||
| /// <summary> Gets a GuildPermissions that grants all permissions. </summary> | /// <summary> Gets a GuildPermissions that grants all permissions. </summary> | ||||
| #if CSHARP7 | |||||
| public static readonly GuildPermissions All = new GuildPermissions(0b000111_111111_0011111111_0000111111); | public static readonly GuildPermissions All = new GuildPermissions(0b000111_111111_0011111111_0000111111); | ||||
| #else | |||||
| public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("00011111111100111111110000111111", 2)); | |||||
| #endif | |||||
| /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | ||||
| public ulong RawValue { get; } | public ulong RawValue { get; } | ||||
| @@ -130,6 +130,7 @@ namespace Discord | |||||
| if (overwrites.TryGetValue(user.Id, out entry)) | if (overwrites.TryGetValue(user.Id, out entry)) | ||||
| resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue; | resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue; | ||||
| #if CSHARP7 | |||||
| switch (channel) | switch (channel) | ||||
| { | { | ||||
| case ITextChannel _: | case ITextChannel _: | ||||
| @@ -140,10 +141,16 @@ namespace Discord | |||||
| if (!GetValue(resolvedPermissions, ChannelPermission.Connect)) | if (!GetValue(resolvedPermissions, ChannelPermission.Connect)) | ||||
| resolvedPermissions = 0; //No read permission on a text channel removes all other permissions | resolvedPermissions = 0; //No read permission on a text channel removes all other permissions | ||||
| break; | break; | ||||
| default: | |||||
| resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) | |||||
| break; | |||||
| } | } | ||||
| #else | |||||
| var textChannel = channel as ITextChannel; | |||||
| var voiceChannel = channel as IVoiceChannel; | |||||
| if (textChannel != null && !GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) | |||||
| resolvedPermissions = 0; //No read permission on a text channel removes all other permissions | |||||
| else if (voiceChannel != null && !GetValue(resolvedPermissions, ChannelPermission.Connect)) | |||||
| resolvedPermissions = 0; //No connect permission on a voice channel removes all other permissions | |||||
| #endif | |||||
| resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) | |||||
| } | } | ||||
| return resolvedPermissions; | return resolvedPermissions; | ||||
| @@ -2,7 +2,7 @@ | |||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using Model = Discord.API.Connection; | using Model = Discord.API.Connection; | ||||
| namespace Discord.Rest | |||||
| namespace Discord | |||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class Connection : IConnection | public class Connection : IConnection | ||||
| @@ -1,8 +0,0 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IDMUser : IUser | |||||
| { | |||||
| /// <summary> Gets the private channel with this user. </summary> | |||||
| IDMChannel Channel { get; } | |||||
| } | |||||
| } | |||||
| @@ -29,9 +29,6 @@ namespace Discord | |||||
| /// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary> | /// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary> | ||||
| ChannelPermissions GetPermissions(IGuildChannel channel); | ChannelPermissions GetPermissions(IGuildChannel channel); | ||||
| /// <summary> Return true if this user has the provided role. </summary> | |||||
| bool HasRole(IRole role); | |||||
| /// <summary> Kicks this user from this guild. </summary> | /// <summary> Kicks this user from this guild. </summary> | ||||
| Task Kick(); | Task Kick(); | ||||
| /// <summary> Modifies this user's properties in this guild. </summary> | /// <summary> Modifies this user's properties in this guild. </summary> | ||||
| @@ -17,6 +17,7 @@ namespace Discord | |||||
| /// <summary> Gets the username for this user. </summary> | /// <summary> Gets the username for this user. </summary> | ||||
| string Username { get; } | string Username { get; } | ||||
| //TODO: CreateDMChannel is a candidate to move to IGuildUser, and User made a common class, depending on next friends list update | |||||
| /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | ||||
| Task<IDMChannel> CreateDMChannel(); | Task<IDMChannel> CreateDMChannel(); | ||||
| } | } | ||||
| @@ -64,11 +64,11 @@ namespace Discord | |||||
| } | } | ||||
| /// <summary> Gets the ids of all users mentioned in a provided text.</summary> | /// <summary> Gets the ids of all users mentioned in a provided text.</summary> | ||||
| public static IReadOnlyList<ulong> GetUserMentions(string text) => GetMentions(text, _userRegex).ToArray(); | |||||
| public static IImmutableList<ulong> GetUserMentions(string text) => GetMentions(text, _userRegex).ToImmutableArray(); | |||||
| /// <summary> Gets the ids of all channels mentioned in a provided text.</summary> | /// <summary> Gets the ids of all channels mentioned in a provided text.</summary> | ||||
| public static IReadOnlyList<ulong> GetChannelMentions(string text) => GetMentions(text, _channelRegex).ToArray(); | |||||
| public static IImmutableList<ulong> GetChannelMentions(string text) => GetMentions(text, _channelRegex).ToImmutableArray(); | |||||
| /// <summary> Gets the ids of all roles mentioned in a provided text.</summary> | /// <summary> Gets the ids of all roles mentioned in a provided text.</summary> | ||||
| public static IReadOnlyList<ulong> GetRoleMentions(string text) => GetMentions(text, _roleRegex).ToArray(); | |||||
| public static IImmutableList<ulong> GetRoleMentions(string text) => GetMentions(text, _roleRegex).ToImmutableArray(); | |||||
| private static ImmutableArray<ulong>.Builder GetMentions(string text, Regex regex) | private static ImmutableArray<ulong>.Builder GetMentions(string text, Regex regex) | ||||
| { | { | ||||
| var matches = regex.Matches(text); | var matches = regex.Matches(text); | ||||
| @@ -1,243 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | |||||
| <PropertyGroup> | |||||
| <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||||
| <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||||
| <ProjectGuid>{18F6FE23-73F6-4CA6-BBD9-F0139DC3EE90}</ProjectGuid> | |||||
| <OutputType>Library</OutputType> | |||||
| <AppDesignerFolder>Properties</AppDesignerFolder> | |||||
| <RootNamespace>Discord</RootNamespace> | |||||
| <AssemblyName>Discord.Net</AssemblyName> | |||||
| <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||||
| <FileAlignment>512</FileAlignment> | |||||
| <TargetFrameworkProfile /> | |||||
| </PropertyGroup> | |||||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||||
| <DebugSymbols>true</DebugSymbols> | |||||
| <DebugType>full</DebugType> | |||||
| <Optimize>false</Optimize> | |||||
| <OutputPath>bin\Debug\</OutputPath> | |||||
| <DefineConstants>TRACE;DEBUG;__DEMO__,__DEMO_EXPERIMENTAL__</DefineConstants> | |||||
| <ErrorReport>prompt</ErrorReport> | |||||
| <WarningLevel>4</WarningLevel> | |||||
| </PropertyGroup> | |||||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||||
| <DebugType>pdbonly</DebugType> | |||||
| <Optimize>true</Optimize> | |||||
| <OutputPath>bin\Release\</OutputPath> | |||||
| <DefineConstants>TRACE;__DEMO__,__DEMO_EXPERIMENTAL__</DefineConstants> | |||||
| <ErrorReport>prompt</ErrorReport> | |||||
| <WarningLevel>4</WarningLevel> | |||||
| </PropertyGroup> | |||||
| <ItemGroup> | |||||
| <Reference Include="System" /> | |||||
| <Reference Include="System.Core" /> | |||||
| <Reference Include="System.Xml.Linq" /> | |||||
| <Reference Include="System.Data.DataSetExtensions" /> | |||||
| <Reference Include="Microsoft.CSharp" /> | |||||
| <Reference Include="System.Data" /> | |||||
| <Reference Include="System.Net.Http" /> | |||||
| <Reference Include="System.Xml" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <Compile Include="API\CDN.cs" /> | |||||
| <Compile Include="API\Common\Attachment.cs" /> | |||||
| <Compile Include="API\Common\Channel.cs" /> | |||||
| <Compile Include="API\Common\Connection.cs" /> | |||||
| <Compile Include="API\Common\Embed.cs" /> | |||||
| <Compile Include="API\Common\EmbedProvider.cs" /> | |||||
| <Compile Include="API\Common\EmbedThumbnail.cs" /> | |||||
| <Compile Include="API\Common\Emoji.cs" /> | |||||
| <Compile Include="API\Common\Game.cs" /> | |||||
| <Compile Include="API\Common\Guild.cs" /> | |||||
| <Compile Include="API\Common\GuildEmbed.cs" /> | |||||
| <Compile Include="API\Common\GuildMember.cs" /> | |||||
| <Compile Include="API\Common\Integration.cs" /> | |||||
| <Compile Include="API\Common\IntegrationAccount.cs" /> | |||||
| <Compile Include="API\Common\Invite.cs" /> | |||||
| <Compile Include="API\Common\InviteChannel.cs" /> | |||||
| <Compile Include="API\Common\InviteGuild.cs" /> | |||||
| <Compile Include="API\Common\InviteMetadata.cs" /> | |||||
| <Compile Include="API\Common\Message.cs" /> | |||||
| <Compile Include="API\Common\Overwrite.cs" /> | |||||
| <Compile Include="API\Common\ReadState.cs" /> | |||||
| <Compile Include="API\Common\Role.cs" /> | |||||
| <Compile Include="API\Common\User.cs" /> | |||||
| <Compile Include="API\Common\UserGuild.cs" /> | |||||
| <Compile Include="API\Common\VoiceRegion.cs" /> | |||||
| <Compile Include="API\Common\VoiceState.cs" /> | |||||
| <Compile Include="API\ImageAttribute.cs" /> | |||||
| <Compile Include="API\IOptional.cs" /> | |||||
| <Compile Include="API\IWebSocketMessage.cs" /> | |||||
| <Compile Include="API\Optional.cs" /> | |||||
| <Compile Include="API\Rest\DeleteMessagesParam.cs" /> | |||||
| <Compile Include="API\Rest\GetGuildMembersParams.cs" /> | |||||
| <Compile Include="API\Rest\LoginParams.cs" /> | |||||
| <Compile Include="API\Rest\LoginResponse.cs" /> | |||||
| <Compile Include="API\Rest\ModifyCurrentUserNickParams.cs" /> | |||||
| <Compile Include="API\Rest\UploadFileParams.cs" /> | |||||
| <Compile Include="API\Rest\GuildPruneParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateChannelInviteParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateDMChannelParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateGuildParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateGuildBanParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateGuildChannelParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateGuildIntegrationParams.cs" /> | |||||
| <Compile Include="API\Rest\CreateMessageParams.cs" /> | |||||
| <Compile Include="API\Rest\GetChannelMessagesParams.cs" /> | |||||
| <Compile Include="API\Rest\GetGatewayResponse.cs" /> | |||||
| <Compile Include="API\Rest\GetGuildPruneCountResponse.cs" /> | |||||
| <Compile Include="API\Rest\ModifyChannelPermissionsParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyCurrentUserParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildChannelsParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildChannelParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildEmbedParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildIntegrationParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildMemberParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildRolesParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyGuildRoleParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyMessageParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyTextChannelParams.cs" /> | |||||
| <Compile Include="API\Rest\ModifyVoiceChannelParams.cs" /> | |||||
| <Compile Include="API\WebSocketMessage.cs" /> | |||||
| <Compile Include="Common\Entities\Users\Game.cs" /> | |||||
| <Compile Include="Common\Entities\Users\StreamType.cs" /> | |||||
| <Compile Include="DiscordConfig.cs" /> | |||||
| <Compile Include="API\DiscordAPIClient.cs" /> | |||||
| <Compile Include="API\Int53Attribute.cs" /> | |||||
| <Compile Include="Preconditions.cs" /> | |||||
| <Compile Include="Net\Converters\DiscordContractResolver.cs" /> | |||||
| <Compile Include="Net\Converters\OptionalConverter.cs" /> | |||||
| <Compile Include="Net\RateLimitException.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\BucketGroup.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\GlobalBucket.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\GuildBucket.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\RequestQueue.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\RequestQueueBucket.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\RestRequest.cs" /> | |||||
| <Compile Include="Rest\DiscordClient.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\IGuildEmbed.cs" /> | |||||
| <Compile Include="Common\Entities\Users\IConnection.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\IGuildIntegration.cs" /> | |||||
| <Compile Include="Common\Entities\Messages\Attachment.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\IChannel.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\IDMChannel.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\IGuildChannel.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\IMessageChannel.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\ITextChannel.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\IVoiceChannel.cs" /> | |||||
| <Compile Include="Common\Entities\Channels\ChannelType.cs" /> | |||||
| <Compile Include="Common\Entities\Roles\Color.cs" /> | |||||
| <Compile Include="Common\Entities\Messages\Direction.cs" /> | |||||
| <Compile Include="Common\Entities\Messages\Embed.cs" /> | |||||
| <Compile Include="Common\Entities\Messages\EmbedProvider.cs" /> | |||||
| <Compile Include="Common\Entities\Messages\EmbedThumbnail.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\Emoji.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\IUserGuild.cs" /> | |||||
| <Compile Include="Common\Entities\IDeletable.cs" /> | |||||
| <Compile Include="Common\Entities\IEntity.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\IGuild.cs" /> | |||||
| <Compile Include="Common\Entities\IMentionable.cs" /> | |||||
| <Compile Include="Common\Entities\Messages\IMessage.cs" /> | |||||
| <Compile Include="Common\Entities\Invites\IInviteMetadata.cs" /> | |||||
| <Compile Include="Common\Entities\Invites\IInvite.cs" /> | |||||
| <Compile Include="Common\Entities\Roles\IRole.cs" /> | |||||
| <Compile Include="Common\Entities\ISnowflakeEntity.cs" /> | |||||
| <Compile Include="Common\Entities\IUpdateable.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\IVoiceRegion.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\ChannelPermission.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\GuildPermission.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\ChannelPermissions.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\GuildPermissions.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\Overwrite.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\OverwritePermissions.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\Permissions.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\PermissionTarget.cs" /> | |||||
| <Compile Include="Common\Entities\Permissions\PermValue.cs" /> | |||||
| <Compile Include="Common\Entities\Users\UserStatus.cs" /> | |||||
| <Compile Include="Common\Entities\Users\IDMUser.cs" /> | |||||
| <Compile Include="Common\Entities\Users\IGuildUser.cs" /> | |||||
| <Compile Include="Common\Entities\Users\ISelfUser.cs" /> | |||||
| <Compile Include="Common\Entities\Users\IUser.cs" /> | |||||
| <Compile Include="Rest\Entities\Guilds\Guild.cs" /> | |||||
| <Compile Include="Rest\Entities\Channels\GuildChannel.cs" /> | |||||
| <Compile Include="Rest\Entities\Channels\TextChannel.cs" /> | |||||
| <Compile Include="Rest\Entities\Channels\VoiceChannel.cs" /> | |||||
| <Compile Include="Rest\Entities\Guilds\GuildEmbed.cs" /> | |||||
| <Compile Include="Rest\Entities\Guilds\GuildIntegration.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\IntegrationAccount.cs" /> | |||||
| <Compile Include="Common\Entities\Users\Connection.cs" /> | |||||
| <Compile Include="Common\Entities\Invites\Invite.cs" /> | |||||
| <Compile Include="Rest\Entities\Message.cs" /> | |||||
| <Compile Include="Rest\Entities\Role.cs" /> | |||||
| <Compile Include="Rest\Entities\Guilds\UserGuild.cs" /> | |||||
| <Compile Include="Rest\Entities\Users\DMUser.cs" /> | |||||
| <Compile Include="Rest\Entities\Users\GuildUser.cs" /> | |||||
| <Compile Include="Rest\Entities\Users\PublicUser.cs" /> | |||||
| <Compile Include="Common\Entities\Guilds\VoiceRegion.cs" /> | |||||
| <Compile Include="Common\Events\LogMessageEventArgs.cs" /> | |||||
| <Compile Include="Common\Events\SentRequestEventArgs.cs" /> | |||||
| <Compile Include="Common\DateTimeUtils.cs" /> | |||||
| <Compile Include="Common\EventExtensions.cs" /> | |||||
| <Compile Include="Common\MentionUtils.cs" /> | |||||
| <Compile Include="IDiscordClient.cs" /> | |||||
| <Compile Include="Logging\ILogger.cs" /> | |||||
| <Compile Include="Logging\Logger.cs" /> | |||||
| <Compile Include="Logging\LogManager.cs" /> | |||||
| <Compile Include="LogSeverity.cs" /> | |||||
| <Compile Include="Net\Converters\ChannelTypeConverter.cs" /> | |||||
| <Compile Include="Net\Converters\ImageConverter.cs" /> | |||||
| <Compile Include="Net\Converters\NullableUInt64Converter.cs" /> | |||||
| <Compile Include="Net\Converters\PermissionTargetConverter.cs" /> | |||||
| <Compile Include="Net\Converters\StringEntityConverter.cs" /> | |||||
| <Compile Include="Net\Converters\UInt64ArrayConverter.cs" /> | |||||
| <Compile Include="Net\Converters\UInt64Converter.cs" /> | |||||
| <Compile Include="Net\Converters\UInt64EntityConverter.cs" /> | |||||
| <Compile Include="Net\Converters\UserStatusConverter.cs" /> | |||||
| <Compile Include="Net\HttpException.cs" /> | |||||
| <Compile Include="Net\Rest\DefaultRestClient.cs" /> | |||||
| <Compile Include="Net\Rest\RequestQueue\IRequestQueue.cs" /> | |||||
| <Compile Include="Net\Rest\IRestClient.cs" /> | |||||
| <Compile Include="Net\Rest\MultipartFile.cs" /> | |||||
| <Compile Include="Net\Rest\RestClientProvider.cs" /> | |||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | |||||
| <Compile Include="Rest\Entities\Channels\DMChannel.cs" /> | |||||
| <Compile Include="Rest\Entities\Users\SelfUser.cs" /> | |||||
| <Compile Include="Rest\Entities\Users\User.cs" /> | |||||
| <Compile Include="TokenType.cs" /> | |||||
| <Compile Include="WebSocket\MessageCache.cs" /> | |||||
| <Compile Include="WebSocket\PermissionsCache.cs" /> | |||||
| <Compile Include="WebSocket\DiscordClient.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Channels\DMChannel.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Channels\GuildChannel.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Channels\TextChannel.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Channels\VoiceChannel.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Guilds\Guild.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Guilds\GuildIntegration.cs" /> | |||||
| <Compile Include="Common\Entities\Invites\InviteMetadata.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Users\DMUser.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Users\GuildUser.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Users\PublicUser.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Users\SelfUser.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Users\User.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Message.cs" /> | |||||
| <Compile Include="WebSocket\Entities\Role.cs" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <None Include="Common\Entities\Users\IVoiceState.cs.old" /> | |||||
| <None Include="project.json" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <Folder Include="Net\WebSockets\" /> | |||||
| </ItemGroup> | |||||
| <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||||
| <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||||
| Other similar extension points exist, see Microsoft.Common.targets. | |||||
| <Target Name="BeforeBuild"> | |||||
| </Target> | |||||
| <Target Name="AfterBuild"> | |||||
| </Target> | |||||
| --> | |||||
| </Project> | |||||
| @@ -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> | |||||
| @@ -10,7 +10,7 @@ namespace Discord | |||||
| public interface IDiscordClient | public interface IDiscordClient | ||||
| { | { | ||||
| TokenType AuthTokenType { get; } | TokenType AuthTokenType { get; } | ||||
| DiscordAPIClient APIClient { get; } | |||||
| DiscordApiClient ApiClient { get; } | |||||
| IRestClient RestClient { get; } | IRestClient RestClient { get; } | ||||
| IRequestQueue RequestQueue { get; } | IRequestQueue RequestQueue { get; } | ||||
| @@ -36,6 +36,5 @@ namespace Discord | |||||
| Task<IEnumerable<IVoiceRegion>> GetVoiceRegions(); | Task<IEnumerable<IVoiceRegion>> GetVoiceRegions(); | ||||
| Task<IVoiceRegion> GetVoiceRegion(string id); | Task<IVoiceRegion> GetVoiceRegion(string id); | ||||
| Task<IVoiceRegion> GetOptimalVoiceRegion(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -3,61 +3,73 @@ using Newtonsoft.Json; | |||||
| using Newtonsoft.Json.Serialization; | using Newtonsoft.Json.Serialization; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq.Expressions; | |||||
| using System.Linq; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| namespace Discord.Net.Converters | namespace Discord.Net.Converters | ||||
| { | { | ||||
| public class DiscordContractResolver : DefaultContractResolver | public class DiscordContractResolver : DefaultContractResolver | ||||
| { | |||||
| { | |||||
| private static readonly TypeInfo _ienumerable = typeof(IEnumerable<ulong[]>).GetTypeInfo(); | |||||
| protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | ||||
| { | { | ||||
| var property = base.CreateProperty(member, memberSerialization); | var property = base.CreateProperty(member, memberSerialization); | ||||
| var type = property.PropertyType; | |||||
| JsonConverter converter = null; | |||||
| var propInfo = member as PropertyInfo; | |||||
| if (member.MemberType == MemberTypes.Property) | |||||
| if (propInfo != null) | |||||
| { | { | ||||
| JsonConverter converter = null; | |||||
| var type = property.PropertyType; | |||||
| var typeInfo = type.GetTypeInfo(); | |||||
| //Primitives | //Primitives | ||||
| if (type == typeof(ulong) && member.GetCustomAttribute<Int53Attribute>() == null) | |||||
| converter = UInt64Converter.Instance; | |||||
| else if (type == typeof(ulong?) && member.GetCustomAttribute<Int53Attribute>() == null) | |||||
| converter = NullableUInt64Converter.Instance; | |||||
| else if (typeof(IEnumerable<ulong[]>).IsAssignableFrom(type) && member.GetCustomAttribute<Int53Attribute>() == null) | |||||
| converter = NullableUInt64Converter.Instance; | |||||
| if (propInfo.GetCustomAttribute<Int53Attribute>() == null) | |||||
| { | |||||
| if (type == typeof(ulong)) | |||||
| converter = UInt64Converter.Instance; | |||||
| else if (type == typeof(ulong?)) | |||||
| converter = NullableUInt64Converter.Instance; | |||||
| else if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEnumerable<ulong>))) | |||||
| converter = UInt64ArrayConverter.Instance; | |||||
| } | |||||
| if (converter == null) | |||||
| { | |||||
| //Enums | |||||
| if (type == typeof(ChannelType)) | |||||
| converter = ChannelTypeConverter.Instance; | |||||
| else if (type == typeof(PermissionTarget)) | |||||
| converter = PermissionTargetConverter.Instance; | |||||
| else if (type == typeof(UserStatus)) | |||||
| converter = UserStatusConverter.Instance; | |||||
| //Enums | |||||
| else if (type == typeof(ChannelType)) | |||||
| converter = ChannelTypeConverter.Instance; | |||||
| else if (type == typeof(PermissionTarget)) | |||||
| converter = PermissionTargetConverter.Instance; | |||||
| else if (type == typeof(UserStatus)) | |||||
| converter = UserStatusConverter.Instance; | |||||
| //Entities | |||||
| if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity<ulong>))) | |||||
| converter = UInt64EntityConverter.Instance; | |||||
| else if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity<string>))) | |||||
| converter = StringEntityConverter.Instance; | |||||
| //Entities | |||||
| else if (typeof(IEntity<ulong>).IsAssignableFrom(type)) | |||||
| converter = UInt64EntityConverter.Instance; | |||||
| else if (typeof(IEntity<string>).IsAssignableFrom(type)) | |||||
| converter = StringEntityConverter.Instance; | |||||
| //Special | |||||
| else if (type == typeof(string) && propInfo.GetCustomAttribute<ImageAttribute>() != null) | |||||
| converter = ImageConverter.Instance; | |||||
| else if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) | |||||
| { | |||||
| var lambda = (Func<object, bool>)propInfo.GetMethod.CreateDelegate(typeof(Func<object, bool>)); | |||||
| /*var parentArg = Expression.Parameter(typeof(object)); | |||||
| var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); | |||||
| var isSpecified = Expression.Property(optional, OptionalConverter.IsSpecifiedProperty); | |||||
| var lambda = Expression.Lambda<Func<object, bool>>(isSpecified, parentArg).Compile();*/ | |||||
| property.ShouldSerialize = x => lambda(x); | |||||
| converter = OptionalConverter.Instance; | |||||
| } | |||||
| } | |||||
| //Special | |||||
| else if (type == typeof(string) && member.GetCustomAttribute<ImageAttribute>() != null) | |||||
| converter = ImageConverter.Instance; | |||||
| else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) | |||||
| if (converter != null) | |||||
| { | { | ||||
| var parentArg = Expression.Parameter(typeof(object)); | |||||
| var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); | |||||
| var isSpecified = Expression.Property(optional, OptionalConverter.IsSpecifiedProperty); | |||||
| var lambda = Expression.Lambda<Func<object, bool>>(isSpecified, parentArg).Compile(); | |||||
| property.ShouldSerialize = x => lambda(x); | |||||
| converter = OptionalConverter.Instance; | |||||
| property.Converter = converter; | |||||
| property.MemberConverter = converter; | |||||
| } | } | ||||
| } | } | ||||
| if (converter != null) | |||||
| { | |||||
| property.Converter = converter; | |||||
| property.MemberConverter = converter; | |||||
| } | |||||
| return property; | return property; | ||||
| } | } | ||||
| @@ -8,7 +8,7 @@ namespace Discord.Net.Converters | |||||
| public class OptionalConverter : JsonConverter | public class OptionalConverter : JsonConverter | ||||
| { | { | ||||
| public static readonly OptionalConverter Instance = new OptionalConverter(); | public static readonly OptionalConverter Instance = new OptionalConverter(); | ||||
| internal static readonly PropertyInfo IsSpecifiedProperty = typeof(IOptional).GetProperty(nameof(IOptional.IsSpecified)); | |||||
| internal static readonly PropertyInfo IsSpecifiedProperty = typeof(IOptional).GetTypeInfo().GetDeclaredProperty(nameof(IOptional.IsSpecified)); | |||||
| public override bool CanConvert(Type objectType) => true; | public override bool CanConvert(Type objectType) => true; | ||||
| public override bool CanRead => false; | public override bool CanRead => false; | ||||
| @@ -75,6 +75,7 @@ namespace Discord.Net.Rest | |||||
| { | { | ||||
| foreach (var p in multipartParams) | foreach (var p in multipartParams) | ||||
| { | { | ||||
| #if CSHARP7 | |||||
| switch (p.Value) | switch (p.Value) | ||||
| { | { | ||||
| case string value: | case string value: | ||||
| @@ -92,6 +93,22 @@ namespace Discord.Net.Rest | |||||
| default: | default: | ||||
| throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); | throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); | ||||
| } | } | ||||
| #else | |||||
| var stringValue = p.Value as string; | |||||
| if (stringValue != null) { content.Add(new StringContent(stringValue), p.Key); continue; } | |||||
| var byteArrayValue = p.Value as byte[]; | |||||
| if (byteArrayValue != null) { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; } | |||||
| var streamValue = p.Value as Stream; | |||||
| if (streamValue != null) { content.Add(new StreamContent(streamValue), p.Key); continue; } | |||||
| if (p.Value is MultipartFile) | |||||
| { | |||||
| var fileValue = (MultipartFile)p.Value; | |||||
| content.Add(new StreamContent(fileValue.Stream), fileValue.Filename, p.Key); | |||||
| continue; | |||||
| } | |||||
| throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); | |||||
| #endif | |||||
| } | } | ||||
| } | } | ||||
| restRequest.Content = content; | restRequest.Content = content; | ||||
| @@ -101,21 +118,9 @@ namespace Discord.Net.Rest | |||||
| private async Task<Stream> SendInternal(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly) | private async Task<Stream> SendInternal(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly) | ||||
| { | { | ||||
| int retryCount = 0; | |||||
| while (true) | while (true) | ||||
| { | { | ||||
| HttpResponseMessage response; | |||||
| try | |||||
| { | |||||
| response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); | |||||
| } | |||||
| catch (WebException ex) | |||||
| { | |||||
| //The request was aborted: Could not create SSL/TLS secure channel. | |||||
| if (ex.HResult == HR_SECURECHANNELFAILED && retryCount++ < 5) | |||||
| continue; //Retrying seems to fix this somehow? | |||||
| throw; | |||||
| } | |||||
| HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); | |||||
| int statusCode = (int)response.StatusCode; | int statusCode = (int)response.StatusCode; | ||||
| if (statusCode < 200 || statusCode >= 300) //2xx = Success | if (statusCode < 200 || statusCode >= 300) //2xx = Success | ||||
| @@ -1,6 +1,4 @@ | |||||
| using System.Threading; | |||||
| namespace Discord.Net.Rest | |||||
| namespace Discord.Net.Rest | |||||
| { | { | ||||
| public delegate IRestClient RestClientProvider(string baseUrl); | public delegate IRestClient RestClientProvider(string baseUrl); | ||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| using System; | |||||
| namespace Discord.Net.WebSockets | |||||
| { | |||||
| public class BinaryMessageEventArgs : EventArgs | |||||
| { | |||||
| public byte[] Data { get; } | |||||
| public BinaryMessageEventArgs(byte[] data) { } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,180 @@ | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.ComponentModel; | |||||
| using System.IO; | |||||
| using System.Net.WebSockets; | |||||
| using System.Text; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Net.WebSockets | |||||
| { | |||||
| public class DefaultWebSocketClient : IWebSocketClient | |||||
| { | |||||
| public const int ReceiveChunkSize = 12 * 1024; //12KB | |||||
| public const int SendChunkSize = 4 * 1024; //4KB | |||||
| protected const int HR_TIMEOUT = -2147012894; | |||||
| public event EventHandler<BinaryMessageEventArgs> BinaryMessage = delegate { }; | |||||
| public event EventHandler<TextMessageEventArgs> TextMessage = delegate { }; | |||||
| protected readonly ConcurrentQueue<string> _sendQueue; | |||||
| protected readonly ClientWebSocket _client; | |||||
| protected Task _receiveTask, _sendTask; | |||||
| protected CancellationTokenSource _cancelToken; | |||||
| protected bool _isDisposed; | |||||
| public DefaultWebSocketClient() | |||||
| { | |||||
| _sendQueue = new ConcurrentQueue<string>(); | |||||
| _client = new ClientWebSocket(); | |||||
| _client.Options.Proxy = null; | |||||
| _client.Options.KeepAliveInterval = TimeSpan.Zero; | |||||
| } | |||||
| protected virtual void Dispose(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| _client.Dispose(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| public void Dispose() | |||||
| { | |||||
| Dispose(true); | |||||
| } | |||||
| public async Task Connect(string host, CancellationToken cancelToken) | |||||
| { | |||||
| await Disconnect().ConfigureAwait(false); | |||||
| _cancelToken = new CancellationTokenSource(); | |||||
| var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken.Token, cancelToken).Token; | |||||
| await _client.ConnectAsync(new Uri(host), combinedToken).ConfigureAwait(false); | |||||
| _receiveTask = ReceiveAsync(combinedToken); | |||||
| _sendTask = SendAsync(combinedToken); | |||||
| } | |||||
| public async Task Disconnect() | |||||
| { | |||||
| _cancelToken.Cancel(); | |||||
| string ignored; | |||||
| while (_sendQueue.TryDequeue(out ignored)) { } | |||||
| _client.Abort(); | |||||
| var receiveTask = _receiveTask ?? Task.CompletedTask; | |||||
| var sendTask = _sendTask ?? Task.CompletedTask; | |||||
| await Task.WhenAll(receiveTask, sendTask).ConfigureAwait(false); | |||||
| } | |||||
| public void SetHeader(string key, string value) | |||||
| { | |||||
| _client.Options.SetRequestHeader(key, value); | |||||
| } | |||||
| public void QueueMessage(string message) | |||||
| { | |||||
| _sendQueue.Enqueue(message); | |||||
| } | |||||
| //TODO: Check this code | |||||
| private Task ReceiveAsync(CancellationToken cancelToken) | |||||
| { | |||||
| return Task.Run(async () => | |||||
| { | |||||
| var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]); | |||||
| var stream = new MemoryStream(); | |||||
| try | |||||
| { | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| { | |||||
| WebSocketReceiveResult result = null; | |||||
| do | |||||
| { | |||||
| if (cancelToken.IsCancellationRequested) return; | |||||
| try | |||||
| { | |||||
| result = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); | |||||
| } | |||||
| catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) | |||||
| { | |||||
| throw new Exception($"Connection timed out."); | |||||
| } | |||||
| if (result.MessageType == WebSocketMessageType.Close) | |||||
| throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); | |||||
| else | |||||
| stream.Write(buffer.Array, 0, result.Count); | |||||
| } | |||||
| while (result == null || !result.EndOfMessage); | |||||
| var array = stream.ToArray(); | |||||
| if (result.MessageType == WebSocketMessageType.Binary) | |||||
| BinaryMessage(this, new BinaryMessageEventArgs(array)); | |||||
| else if (result.MessageType == WebSocketMessageType.Text) | |||||
| { | |||||
| string text = Encoding.UTF8.GetString(array, 0, array.Length); | |||||
| TextMessage(this, new TextMessageEventArgs(text)); | |||||
| } | |||||
| stream.Position = 0; | |||||
| stream.SetLength(0); | |||||
| } | |||||
| } | |||||
| catch (OperationCanceledException) { } | |||||
| }); | |||||
| } | |||||
| //TODO: Check this code | |||||
| private Task SendAsync(CancellationToken cancelToken) | |||||
| { | |||||
| return Task.Run(async () => | |||||
| { | |||||
| byte[] bytes = new byte[SendChunkSize]; | |||||
| try | |||||
| { | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| { | |||||
| string json; | |||||
| while (_sendQueue.TryDequeue(out json)) | |||||
| { | |||||
| int byteCount = Encoding.UTF8.GetBytes(json, 0, json.Length, bytes, 0); | |||||
| int frameCount = (int)Math.Ceiling((double)byteCount / SendChunkSize); | |||||
| int offset = 0; | |||||
| for (int i = 0; i < frameCount; i++, offset += SendChunkSize) | |||||
| { | |||||
| bool isLast = i == (frameCount - 1); | |||||
| int count; | |||||
| if (isLast) | |||||
| count = byteCount - (i * SendChunkSize); | |||||
| else | |||||
| count = SendChunkSize; | |||||
| try | |||||
| { | |||||
| await _client.SendAsync(new ArraySegment<byte>(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); | |||||
| } | |||||
| catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) | |||||
| { | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| catch (OperationCanceledException) { } | |||||
| }); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| using System; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Net.WebSockets | |||||
| { | |||||
| //TODO: Add ETF | |||||
| public interface IWebSocketClient | |||||
| { | |||||
| event EventHandler<BinaryMessageEventArgs> BinaryMessage; | |||||
| event EventHandler<TextMessageEventArgs> TextMessage; | |||||
| void SetHeader(string key, string value); | |||||
| Task Connect(string host, CancellationToken cancelToken); | |||||
| Task Disconnect(); | |||||
| void QueueMessage(string message); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| using System; | |||||
| namespace Discord.Net.WebSockets | |||||
| { | |||||
| public class TextMessageEventArgs : EventArgs | |||||
| { | |||||
| public string Message { get; } | |||||
| public TextMessageEventArgs(string msg) { Message = msg; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,4 @@ | |||||
| namespace Discord.Net.WebSockets | |||||
| { | |||||
| public delegate IWebSocketClient WebSocketProvider(string baseUrl); | |||||
| } | |||||
| @@ -1,16 +1,19 @@ | |||||
| using System.Reflection; | using System.Reflection; | ||||
| using System.Runtime.CompilerServices; | |||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
| [assembly: AssemblyTitle("Discord.Net")] | |||||
| [assembly: AssemblyProduct("Discord.Net")] | |||||
| [assembly: AssemblyDescription("An unofficial .Net API wrapper for the Discord client.")] | |||||
| [assembly: AssemblyCompany("RogueException")] | |||||
| [assembly: AssemblyCopyright("Copyright © RogueException 2016")] | |||||
| // General Information about an assembly is controlled through the following | |||||
| // set of attributes. Change these attribute values to modify the information | |||||
| // associated with an assembly. | |||||
| [assembly: AssemblyConfiguration("")] | |||||
| [assembly: AssemblyCompany("")] | |||||
| [assembly: AssemblyProduct("Discord.Net.Core")] | |||||
| [assembly: AssemblyTrademark("")] | |||||
| // Setting ComVisible to false makes the types in this assembly not visible | |||||
| // to COM components. If you need to access a type in this assembly from | |||||
| // COM, set the ComVisible attribute to true on that type. | |||||
| [assembly: ComVisible(false)] | [assembly: ComVisible(false)] | ||||
| [assembly: Guid("62ea817d-c945-4100-ba21-9dfb139d2868")] | |||||
| [assembly: AssemblyVersion("1.0.0.0")] | |||||
| [assembly: AssemblyFileVersion("1.0.0.0")] | |||||
| [assembly: AssemblyInformationalVersion("1.0.0-alpha1")] | |||||
| // The following GUID is for the ID of the typelib if this project is exposed to COM | |||||
| [assembly: Guid("91e9e7bd-75c9-4e98-84aa-2c271922e5c2")] | |||||
| @@ -10,25 +10,28 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| //TODO: Docstrings | |||||
| //TODO: Log Internal/External REST Rate Limits, 502s | |||||
| //TODO: Log Logins/Logouts | |||||
| public sealed class DiscordClient : IDiscordClient, IDisposable | public sealed class DiscordClient : IDiscordClient, IDisposable | ||||
| { | { | ||||
| public event EventHandler<LogMessageEventArgs> Log; | public event EventHandler<LogMessageEventArgs> Log; | ||||
| public event EventHandler LoggedIn, LoggedOut; | public event EventHandler LoggedIn, LoggedOut; | ||||
| private readonly Logger _discordLogger, _restLogger; | |||||
| private readonly SemaphoreSlim _connectionLock; | private readonly SemaphoreSlim _connectionLock; | ||||
| private readonly RestClientProvider _restClientProvider; | private readonly RestClientProvider _restClientProvider; | ||||
| private readonly LogManager _log; | private readonly LogManager _log; | ||||
| private CancellationTokenSource _cancelTokenSource; | private CancellationTokenSource _cancelTokenSource; | ||||
| private bool _isDisposed; | private bool _isDisposed; | ||||
| private string _userAgent; | |||||
| private SelfUser _currentUser; | private SelfUser _currentUser; | ||||
| public bool IsLoggedIn { get; private set; } | public bool IsLoggedIn { get; private set; } | ||||
| public API.DiscordAPIClient APIClient { get; private set; } | |||||
| public API.DiscordApiClient ApiClient { get; private set; } | |||||
| public TokenType AuthTokenType => APIClient.AuthTokenType; | |||||
| public IRestClient RestClient => APIClient.RestClient; | |||||
| public IRequestQueue RequestQueue => APIClient.RequestQueue; | |||||
| public TokenType AuthTokenType => ApiClient.AuthTokenType; | |||||
| public IRestClient RestClient => ApiClient.RestClient; | |||||
| public IRequestQueue RequestQueue => ApiClient.RequestQueue; | |||||
| public DiscordClient(DiscordConfig config = null) | public DiscordClient(DiscordConfig config = null) | ||||
| { | { | ||||
| @@ -37,12 +40,14 @@ namespace Discord.Rest | |||||
| _restClientProvider = config.RestClientProvider; | _restClientProvider = config.RestClientProvider; | ||||
| _connectionLock = new SemaphoreSlim(1, 1); | |||||
| _log = new LogManager(config.LogLevel); | _log = new LogManager(config.LogLevel); | ||||
| _userAgent = DiscordConfig.UserAgent; | |||||
| APIClient = new API.DiscordAPIClient(_restClientProvider); | |||||
| _log.Message += (s, e) => Log.Raise(this, e); | |||||
| _discordLogger = _log.CreateLogger("Discord"); | |||||
| _restLogger = _log.CreateLogger("Rest"); | |||||
| _log.Message += (s,e) => Log.Raise(this, e); | |||||
| _connectionLock = new SemaphoreSlim(1, 1); | |||||
| ApiClient = new API.DiscordApiClient(_restClientProvider); | |||||
| ApiClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); | |||||
| } | } | ||||
| public async Task Login(string email, string password) | public async Task Login(string email, string password) | ||||
| @@ -72,7 +77,7 @@ namespace Discord.Rest | |||||
| _cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
| var args = new LoginParams { Email = email, Password = password }; | var args = new LoginParams { Email = email, Password = password }; | ||||
| await APIClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); | |||||
| await ApiClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); | |||||
| await CompleteLogin(false).ConfigureAwait(false); | await CompleteLogin(false).ConfigureAwait(false); | ||||
| } | } | ||||
| catch { await LogoutInternal().ConfigureAwait(false); throw; } | catch { await LogoutInternal().ConfigureAwait(false); throw; } | ||||
| @@ -85,22 +90,20 @@ namespace Discord.Rest | |||||
| { | { | ||||
| _cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
| await APIClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); | |||||
| await ApiClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); | |||||
| await CompleteLogin(validateToken).ConfigureAwait(false); | await CompleteLogin(validateToken).ConfigureAwait(false); | ||||
| } | } | ||||
| catch { await LogoutInternal().ConfigureAwait(false); throw; } | catch { await LogoutInternal().ConfigureAwait(false); throw; } | ||||
| } | } | ||||
| private async Task CompleteLogin(bool validateToken) | private async Task CompleteLogin(bool validateToken) | ||||
| { | { | ||||
| APIClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); | |||||
| if (validateToken) | if (validateToken) | ||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await APIClient.ValidateToken().ConfigureAwait(false); | |||||
| await ApiClient.ValidateToken().ConfigureAwait(false); | |||||
| } | } | ||||
| catch { await APIClient.Logout().ConfigureAwait(false); } | |||||
| catch { await ApiClient.Logout().ConfigureAwait(false); } | |||||
| } | } | ||||
| IsLoggedIn = true; | IsLoggedIn = true; | ||||
| @@ -127,7 +130,7 @@ namespace Discord.Rest | |||||
| catch { } | catch { } | ||||
| } | } | ||||
| await APIClient.Logout().ConfigureAwait(false); | |||||
| await ApiClient.Logout().ConfigureAwait(false); | |||||
| _currentUser = null; | _currentUser = null; | ||||
| if (wasLoggedIn) | if (wasLoggedIn) | ||||
| @@ -139,18 +142,18 @@ namespace Discord.Rest | |||||
| public async Task<IEnumerable<Connection>> GetConnections() | public async Task<IEnumerable<Connection>> GetConnections() | ||||
| { | { | ||||
| var models = await APIClient.GetCurrentUserConnections().ConfigureAwait(false); | |||||
| var models = await ApiClient.GetCurrentUserConnections().ConfigureAwait(false); | |||||
| return models.Select(x => new Connection(x)); | return models.Select(x => new Connection(x)); | ||||
| } | } | ||||
| public async Task<IChannel> GetChannel(ulong id) | public async Task<IChannel> GetChannel(ulong id) | ||||
| { | { | ||||
| var model = await APIClient.GetChannel(id).ConfigureAwait(false); | |||||
| var model = await ApiClient.GetChannel(id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| { | { | ||||
| if (model.GuildId != null) | if (model.GuildId != null) | ||||
| { | { | ||||
| var guildModel = await APIClient.GetGuild(model.GuildId.Value).ConfigureAwait(false); | |||||
| var guildModel = await ApiClient.GetGuild(model.GuildId.Value).ConfigureAwait(false); | |||||
| if (guildModel != null) | if (guildModel != null) | ||||
| { | { | ||||
| var guild = new Guild(this, guildModel); | var guild = new Guild(this, guildModel); | ||||
| @@ -164,13 +167,13 @@ namespace Discord.Rest | |||||
| } | } | ||||
| public async Task<IEnumerable<DMChannel>> GetDMChannels() | public async Task<IEnumerable<DMChannel>> GetDMChannels() | ||||
| { | { | ||||
| var models = await APIClient.GetCurrentUserDMs().ConfigureAwait(false); | |||||
| var models = await ApiClient.GetCurrentUserDMs().ConfigureAwait(false); | |||||
| return models.Select(x => new DMChannel(this, x)); | return models.Select(x => new DMChannel(this, x)); | ||||
| } | } | ||||
| public async Task<Invite> GetInvite(string inviteIdOrXkcd) | public async Task<Invite> GetInvite(string inviteIdOrXkcd) | ||||
| { | { | ||||
| var model = await APIClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||||
| var model = await ApiClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return new Invite(this, model); | return new Invite(this, model); | ||||
| return null; | return null; | ||||
| @@ -178,41 +181,41 @@ namespace Discord.Rest | |||||
| public async Task<Guild> GetGuild(ulong id) | public async Task<Guild> GetGuild(ulong id) | ||||
| { | { | ||||
| var model = await APIClient.GetGuild(id).ConfigureAwait(false); | |||||
| var model = await ApiClient.GetGuild(id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return new Guild(this, model); | return new Guild(this, model); | ||||
| return null; | return null; | ||||
| } | } | ||||
| public async Task<GuildEmbed> GetGuildEmbed(ulong id) | public async Task<GuildEmbed> GetGuildEmbed(ulong id) | ||||
| { | { | ||||
| var model = await APIClient.GetGuildEmbed(id).ConfigureAwait(false); | |||||
| var model = await ApiClient.GetGuildEmbed(id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return new GuildEmbed(model); | return new GuildEmbed(model); | ||||
| return null; | return null; | ||||
| } | } | ||||
| public async Task<IEnumerable<UserGuild>> GetGuilds() | public async Task<IEnumerable<UserGuild>> GetGuilds() | ||||
| { | { | ||||
| var models = await APIClient.GetCurrentUserGuilds().ConfigureAwait(false); | |||||
| var models = await ApiClient.GetCurrentUserGuilds().ConfigureAwait(false); | |||||
| return models.Select(x => new UserGuild(this, x)); | return models.Select(x => new UserGuild(this, x)); | ||||
| } | } | ||||
| public async Task<Guild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) | public async Task<Guild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) | ||||
| { | { | ||||
| var args = new CreateGuildParams(); | var args = new CreateGuildParams(); | ||||
| var model = await APIClient.CreateGuild(args).ConfigureAwait(false); | |||||
| var model = await ApiClient.CreateGuild(args).ConfigureAwait(false); | |||||
| return new Guild(this, model); | return new Guild(this, model); | ||||
| } | } | ||||
| public async Task<User> GetUser(ulong id) | public async Task<User> GetUser(ulong id) | ||||
| { | { | ||||
| var model = await APIClient.GetUser(id).ConfigureAwait(false); | |||||
| var model = await ApiClient.GetUser(id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return new PublicUser(this, model); | return new PublicUser(this, model); | ||||
| return null; | return null; | ||||
| } | } | ||||
| public async Task<User> GetUser(string username, ushort discriminator) | public async Task<User> GetUser(string username, ushort discriminator) | ||||
| { | { | ||||
| var model = await APIClient.GetUser(username, discriminator).ConfigureAwait(false); | |||||
| var model = await ApiClient.GetUser(username, discriminator).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return new PublicUser(this, model); | return new PublicUser(this, model); | ||||
| return null; | return null; | ||||
| @@ -222,7 +225,7 @@ namespace Discord.Rest | |||||
| var user = _currentUser; | var user = _currentUser; | ||||
| if (user == null) | if (user == null) | ||||
| { | { | ||||
| var model = await APIClient.GetCurrentUser().ConfigureAwait(false); | |||||
| var model = await ApiClient.GetCurrentUser().ConfigureAwait(false); | |||||
| user = new SelfUser(this, model); | user = new SelfUser(this, model); | ||||
| _currentUser = user; | _currentUser = user; | ||||
| } | } | ||||
| @@ -230,25 +233,20 @@ namespace Discord.Rest | |||||
| } | } | ||||
| public async Task<IEnumerable<User>> QueryUsers(string query, int limit) | public async Task<IEnumerable<User>> QueryUsers(string query, int limit) | ||||
| { | { | ||||
| var models = await APIClient.QueryUsers(query, limit).ConfigureAwait(false); | |||||
| var models = await ApiClient.QueryUsers(query, limit).ConfigureAwait(false); | |||||
| return models.Select(x => new PublicUser(this, x)); | return models.Select(x => new PublicUser(this, x)); | ||||
| } | } | ||||
| public async Task<IEnumerable<VoiceRegion>> GetVoiceRegions() | public async Task<IEnumerable<VoiceRegion>> GetVoiceRegions() | ||||
| { | { | ||||
| var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); | |||||
| var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false); | |||||
| return models.Select(x => new VoiceRegion(x)); | return models.Select(x => new VoiceRegion(x)); | ||||
| } | } | ||||
| public async Task<VoiceRegion> GetVoiceRegion(string id) | public async Task<VoiceRegion> GetVoiceRegion(string id) | ||||
| { | { | ||||
| var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); | |||||
| var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false); | |||||
| return models.Select(x => new VoiceRegion(x)).Where(x => x.Id == id).FirstOrDefault(); | return models.Select(x => new VoiceRegion(x)).Where(x => x.Id == id).FirstOrDefault(); | ||||
| } | } | ||||
| public async Task<VoiceRegion> GetOptimalVoiceRegion() | |||||
| { | |||||
| var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); | |||||
| return models.Select(x => new VoiceRegion(x)).Where(x => x.IsOptimal).FirstOrDefault(); | |||||
| } | |||||
| void Dispose(bool disposing) | void Dispose(bool disposing) | ||||
| { | { | ||||
| @@ -261,7 +259,7 @@ namespace Discord.Rest | |||||
| } | } | ||||
| public void Dispose() => Dispose(true); | public void Dispose() => Dispose(true); | ||||
| API.DiscordAPIClient IDiscordClient.APIClient => APIClient; | |||||
| API.DiscordApiClient IDiscordClient.ApiClient => ApiClient; | |||||
| async Task<IChannel> IDiscordClient.GetChannel(ulong id) | async Task<IChannel> IDiscordClient.GetChannel(ulong id) | ||||
| => await GetChannel(id).ConfigureAwait(false); | => await GetChannel(id).ConfigureAwait(false); | ||||
| @@ -289,7 +287,5 @@ namespace Discord.Rest | |||||
| => await GetVoiceRegions().ConfigureAwait(false); | => await GetVoiceRegions().ConfigureAwait(false); | ||||
| async Task<IVoiceRegion> IDiscordClient.GetVoiceRegion(string id) | async Task<IVoiceRegion> IDiscordClient.GetVoiceRegion(string id) | ||||
| => await GetVoiceRegion(id).ConfigureAwait(false); | => await GetVoiceRegion(id).ConfigureAwait(false); | ||||
| async Task<IVoiceRegion> IDiscordClient.GetOptimalVoiceRegion() | |||||
| => await GetOptimalVoiceRegion().ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -18,7 +18,7 @@ namespace Discord.Rest | |||||
| internal DiscordClient Discord { get; } | internal DiscordClient Discord { get; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DMUser Recipient { get; private set; } | |||||
| public User Recipient { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| @@ -33,13 +33,13 @@ namespace Discord.Rest | |||||
| private void Update(Model model) | private void Update(Model model) | ||||
| { | { | ||||
| if (Recipient == null) | if (Recipient == null) | ||||
| Recipient = new DMUser(this, model.Recipient); | |||||
| Recipient = new PublicUser(Discord, model.Recipient); | |||||
| else | else | ||||
| Recipient.Update(model.Recipient); | Recipient.Update(model.Recipient); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IUser> GetUser(ulong id) | |||||
| public async Task<User> GetUser(ulong id) | |||||
| { | { | ||||
| var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); | var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); | ||||
| if (id == Recipient.Id) | if (id == Recipient.Id) | ||||
| @@ -50,24 +50,24 @@ namespace Discord.Rest | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IEnumerable<IUser>> GetUsers() | |||||
| public async Task<IEnumerable<User>> GetUsers() | |||||
| { | { | ||||
| var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); | var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); | ||||
| return ImmutableArray.Create<IUser>(currentUser, Recipient); | |||||
| return ImmutableArray.Create(currentUser, Recipient); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IEnumerable<Message>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | public async Task<IEnumerable<Message>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new Message(this, x)); | return models.Select(x => new Message(this, x)); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IEnumerable<Message>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public async Task<IEnumerable<Message>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new Message(this, x)); | return models.Select(x => new Message(this, x)); | ||||
| } | } | ||||
| @@ -75,7 +75,7 @@ namespace Discord.Rest | |||||
| public async Task<Message> SendMessage(string text, bool isTTS = false) | public async Task<Message> SendMessage(string text, bool isTTS = false) | ||||
| { | { | ||||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.CreateDMMessage(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateDMMessage(Id, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | return new Message(this, model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -85,7 +85,7 @@ namespace Discord.Rest | |||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadDMFile(Id, file, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.UploadDMFile(Id, file, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | return new Message(this, model); | ||||
| } | } | ||||
| } | } | ||||
| @@ -93,32 +93,32 @@ namespace Discord.Rest | |||||
| public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | return new Message(this, model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task DeleteMessages(IEnumerable<IMessage> messages) | public async Task DeleteMessages(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task TriggerTyping() | public async Task TriggerTyping() | ||||
| { | { | ||||
| await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Close() | public async Task Close() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Update() | public async Task Update() | ||||
| { | { | ||||
| var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -126,7 +126,7 @@ namespace Discord.Rest | |||||
| public override string ToString() => '@' + Recipient.ToString(); | public override string ToString() => '@' + Recipient.ToString(); | ||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | ||||
| IDMUser IDMChannel.Recipient => Recipient; | |||||
| IUser IDMChannel.Recipient => Recipient; | |||||
| IEnumerable<IMessage> IMessageChannel.CachedMessages => Array.Empty<Message>(); | IEnumerable<IMessage> IMessageChannel.CachedMessages => Array.Empty<Message>(); | ||||
| async Task<IEnumerable<IUser>> IChannel.GetUsers() | async Task<IEnumerable<IUser>> IChannel.GetUsers() | ||||
| @@ -55,7 +55,7 @@ namespace Discord.Rest | |||||
| var args = new ModifyGuildChannelParams(); | var args = new ModifyGuildChannelParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -78,7 +78,7 @@ namespace Discord.Rest | |||||
| /// <summary> Downloads a collection of all invites to this channel. </summary> | /// <summary> Downloads a collection of all invites to this channel. </summary> | ||||
| public async Task<IEnumerable<InviteMetadata>> GetInvites() | public async Task<IEnumerable<InviteMetadata>> GetInvites() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetChannelInvites(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetChannelInvites(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new InviteMetadata(Discord, x)); | return models.Select(x => new InviteMetadata(Discord, x)); | ||||
| } | } | ||||
| @@ -86,20 +86,20 @@ namespace Discord.Rest | |||||
| public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) | public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | ||||
| await Discord.APIClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); | |||||
| _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); | _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) | public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | ||||
| await Discord.APIClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); | |||||
| _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); | _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task RemovePermissionOverwrite(IUser user) | public async Task RemovePermissionOverwrite(IUser user) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); | |||||
| Overwrite value; | Overwrite value; | ||||
| _overwrites.TryRemove(user.Id, out value); | _overwrites.TryRemove(user.Id, out value); | ||||
| @@ -107,7 +107,7 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task RemovePermissionOverwrite(IRole role) | public async Task RemovePermissionOverwrite(IRole role) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); | |||||
| Overwrite value; | Overwrite value; | ||||
| _overwrites.TryRemove(role.Id, out value); | _overwrites.TryRemove(role.Id, out value); | ||||
| @@ -127,19 +127,19 @@ namespace Discord.Rest | |||||
| Temporary = isTemporary, | Temporary = isTemporary, | ||||
| XkcdPass = withXkcd | XkcdPass = withXkcd | ||||
| }; | }; | ||||
| var model = await Discord.APIClient.CreateChannelInvite(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateChannelInvite(Id, args).ConfigureAwait(false); | |||||
| return new InviteMetadata(Discord, model); | return new InviteMetadata(Discord, model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Update() | public async Task Update() | ||||
| { | { | ||||
| var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -35,7 +35,7 @@ namespace Discord.Rest | |||||
| var args = new ModifyTextChannelParams(); | var args = new ModifyTextChannelParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -64,14 +64,14 @@ namespace Discord.Rest | |||||
| public async Task<IEnumerable<Message>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | public async Task<IEnumerable<Message>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new Message(this, x)); | return models.Select(x => new Message(this, x)); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IEnumerable<Message>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public async Task<IEnumerable<Message>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new Message(this, x)); | return models.Select(x => new Message(this, x)); | ||||
| } | } | ||||
| @@ -79,7 +79,7 @@ namespace Discord.Rest | |||||
| public async Task<Message> SendMessage(string text, bool isTTS = false) | public async Task<Message> SendMessage(string text, bool isTTS = false) | ||||
| { | { | ||||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | return new Message(this, model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -89,7 +89,7 @@ namespace Discord.Rest | |||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | return new Message(this, model); | ||||
| } | } | ||||
| } | } | ||||
| @@ -97,20 +97,20 @@ namespace Discord.Rest | |||||
| public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | return new Message(this, model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task DeleteMessages(IEnumerable<IMessage> messages) | public async Task DeleteMessages(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task TriggerTyping() | public async Task TriggerTyping() | ||||
| { | { | ||||
| await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | ||||
| @@ -30,7 +30,7 @@ namespace Discord.Rest | |||||
| var args = new ModifyVoiceChannelParams(); | var args = new ModifyVoiceChannelParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -115,7 +115,7 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Update() | public async Task Update() | ||||
| { | { | ||||
| var response = await Discord.APIClient.GetGuild(Id).ConfigureAwait(false); | |||||
| var response = await Discord.ApiClient.GetGuild(Id).ConfigureAwait(false); | |||||
| Update(response); | Update(response); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -125,7 +125,7 @@ namespace Discord.Rest | |||||
| var args = new ModifyGuildParams(); | var args = new ModifyGuildParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -135,35 +135,35 @@ namespace Discord.Rest | |||||
| var args = new ModifyGuildEmbedParams(); | var args = new ModifyGuildEmbedParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args) | public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args) | ||||
| { | { | ||||
| await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ModifyRoles(IEnumerable<ModifyGuildRolesParams> args) | public async Task ModifyRoles(IEnumerable<ModifyGuildRolesParams> args) | ||||
| { | { | ||||
| var models = await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); | |||||
| Update(models); | Update(models); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Leave() | public async Task Leave() | ||||
| { | { | ||||
| await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IEnumerable<User>> GetBans() | public async Task<IEnumerable<User>> GetBans() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetGuildBans(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildBans(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new PublicUser(Discord, x)); | return models.Select(x => new PublicUser(Discord, x)); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -171,24 +171,21 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task AddBan(ulong userId, int pruneDays = 0) | public async Task AddBan(ulong userId, int pruneDays = 0) | ||||
| { | { | ||||
| var args = new CreateGuildBanParams() | |||||
| { | |||||
| PruneDays = pruneDays | |||||
| }; | |||||
| await Discord.APIClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); | |||||
| var args = new CreateGuildBanParams() { PruneDays = pruneDays }; | |||||
| await Discord.ApiClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task RemoveBan(IUser user) => RemoveBan(user.Id); | public Task RemoveBan(IUser user) => RemoveBan(user.Id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task RemoveBan(ulong userId) | public async Task RemoveBan(ulong userId) | ||||
| { | { | ||||
| await Discord.APIClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); | |||||
| await Discord.ApiClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <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> | ||||
| public async Task<GuildChannel> GetChannel(ulong id) | public async Task<GuildChannel> GetChannel(ulong id) | ||||
| { | { | ||||
| var model = await Discord.APIClient.GetChannel(Id, id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.GetChannel(Id, id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return ToChannel(model); | return ToChannel(model); | ||||
| return null; | return null; | ||||
| @@ -196,7 +193,7 @@ namespace Discord.Rest | |||||
| /// <summary> Gets a collection of all channels in this guild. </summary> | /// <summary> Gets a collection of all channels in this guild. </summary> | ||||
| public async Task<IEnumerable<GuildChannel>> GetChannels() | public async Task<IEnumerable<GuildChannel>> GetChannels() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetGuildChannels(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildChannels(Id).ConfigureAwait(false); | |||||
| return models.Select(x => ToChannel(x)); | return models.Select(x => ToChannel(x)); | ||||
| } | } | ||||
| /// <summary> Creates a new text channel. </summary> | /// <summary> Creates a new text channel. </summary> | ||||
| @@ -205,7 +202,7 @@ namespace Discord.Rest | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; | var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; | ||||
| var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| return new TextChannel(this, model); | return new TextChannel(this, model); | ||||
| } | } | ||||
| /// <summary> Creates a new voice channel. </summary> | /// <summary> Creates a new voice channel. </summary> | ||||
| @@ -214,28 +211,28 @@ namespace Discord.Rest | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; | var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; | ||||
| var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| return new VoiceChannel(this, model); | return new VoiceChannel(this, model); | ||||
| } | } | ||||
| /// <summary> Gets a collection of all integrations attached to this guild. </summary> | /// <summary> Gets a collection of all integrations attached to this guild. </summary> | ||||
| public async Task<IEnumerable<GuildIntegration>> GetIntegrations() | public async Task<IEnumerable<GuildIntegration>> GetIntegrations() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetGuildIntegrations(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildIntegrations(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new GuildIntegration(this, x)); | return models.Select(x => new GuildIntegration(this, x)); | ||||
| } | } | ||||
| /// <summary> Creates a new integration for this guild. </summary> | /// <summary> Creates a new integration for this guild. </summary> | ||||
| public async Task<GuildIntegration> CreateIntegration(ulong id, string type) | public async Task<GuildIntegration> CreateIntegration(ulong id, string type) | ||||
| { | { | ||||
| var args = new CreateGuildIntegrationParams { Id = id, Type = type }; | var args = new CreateGuildIntegrationParams { Id = id, Type = type }; | ||||
| var model = await Discord.APIClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); | |||||
| return new GuildIntegration(this, model); | return new GuildIntegration(this, model); | ||||
| } | } | ||||
| /// <summary> Gets a collection of all invites to this guild. </summary> | /// <summary> Gets a collection of all invites to this guild. </summary> | ||||
| public async Task<IEnumerable<InviteMetadata>> GetInvites() | public async Task<IEnumerable<InviteMetadata>> GetInvites() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetGuildInvites(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildInvites(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new InviteMetadata(Discord, x)); | return models.Select(x => new InviteMetadata(Discord, x)); | ||||
| } | } | ||||
| /// <summary> Creates a new invite to this guild. </summary> | /// <summary> Creates a new invite to this guild. </summary> | ||||
| @@ -251,7 +248,7 @@ namespace Discord.Rest | |||||
| Temporary = isTemporary, | Temporary = isTemporary, | ||||
| XkcdPass = withXkcd | XkcdPass = withXkcd | ||||
| }; | }; | ||||
| var model = await Discord.APIClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); | |||||
| return new InviteMetadata(Discord, model); | return new InviteMetadata(Discord, model); | ||||
| } | } | ||||
| @@ -269,7 +266,7 @@ namespace Discord.Rest | |||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| var model = await Discord.APIClient.CreateGuildRole(Id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildRole(Id).ConfigureAwait(false); | |||||
| var role = new Role(this, model); | var role = new Role(this, model); | ||||
| await role.Modify(x => | await role.Modify(x => | ||||
| @@ -287,20 +284,20 @@ namespace Discord.Rest | |||||
| public async Task<IEnumerable<GuildUser>> GetUsers() | public async Task<IEnumerable<GuildUser>> GetUsers() | ||||
| { | { | ||||
| var args = new GetGuildMembersParams(); | var args = new GetGuildMembersParams(); | ||||
| var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new GuildUser(this, x)); | return models.Select(x => new GuildUser(this, x)); | ||||
| } | } | ||||
| /// <summary> Gets a paged collection of all users in this guild. </summary> | /// <summary> Gets a paged collection of all users in this guild. </summary> | ||||
| public async Task<IEnumerable<GuildUser>> GetUsers(int limit, int offset) | public async Task<IEnumerable<GuildUser>> GetUsers(int limit, int offset) | ||||
| { | { | ||||
| var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; | var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; | ||||
| var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new GuildUser(this, x)); | return models.Select(x => new GuildUser(this, x)); | ||||
| } | } | ||||
| /// <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> | ||||
| public async Task<GuildUser> GetUser(ulong id) | public async Task<GuildUser> GetUser(ulong id) | ||||
| { | { | ||||
| var model = await Discord.APIClient.GetGuildMember(Id, id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.GetGuildMember(Id, id).ConfigureAwait(false); | |||||
| if (model != null) | if (model != null) | ||||
| return new GuildUser(this, model); | return new GuildUser(this, model); | ||||
| return null; | return null; | ||||
| @@ -316,9 +313,9 @@ namespace Discord.Rest | |||||
| var args = new GuildPruneParams() { Days = days }; | var args = new GuildPruneParams() { Days = days }; | ||||
| GetGuildPruneCountResponse model; | GetGuildPruneCountResponse model; | ||||
| if (simulate) | if (simulate) | ||||
| model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); | |||||
| model = await Discord.ApiClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); | |||||
| else | else | ||||
| model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false); | |||||
| model = await Discord.ApiClient.BeginGuildPrune(Id, args).ConfigureAwait(false); | |||||
| return model.Pruned; | return model.Pruned; | ||||
| } | } | ||||
| @@ -60,7 +60,7 @@ namespace Discord.Rest | |||||
| /// <summary> </summary> | /// <summary> </summary> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> </summary> | /// <summary> </summary> | ||||
| public async Task Modify(Action<ModifyGuildIntegrationParams> func) | public async Task Modify(Action<ModifyGuildIntegrationParams> func) | ||||
| @@ -69,14 +69,14 @@ namespace Discord.Rest | |||||
| var args = new ModifyGuildIntegrationParams(); | var args = new ModifyGuildIntegrationParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| /// <summary> </summary> | /// <summary> </summary> | ||||
| public async Task Sync() | public async Task Sync() | ||||
| { | { | ||||
| await Discord.APIClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| @@ -42,12 +42,12 @@ namespace Discord | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Leave() | public async Task Leave() | ||||
| { | { | ||||
| await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| @@ -28,14 +28,14 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IMessageChannel Channel { get; } | public IMessageChannel Channel { get; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public User Author { get; } | |||||
| public IUser Author { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<Attachment> Attachments { get; private set; } | public IReadOnlyList<Attachment> Attachments { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<Embed> Embeds { get; private set; } | public IReadOnlyList<Embed> Embeds { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<PublicUser> MentionedUsers { get; private set; } | |||||
| public IReadOnlyList<IUser> MentionedUsers { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<ulong> MentionedChannelIds { get; private set; } | public IReadOnlyList<ulong> MentionedChannelIds { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -55,6 +55,10 @@ namespace Discord.Rest | |||||
| } | } | ||||
| private void Update(Model model) | private void Update(Model model) | ||||
| { | { | ||||
| var guildChannel = Channel as GuildChannel; | |||||
| var guild = guildChannel?.Guild; | |||||
| var discord = Discord; | |||||
| IsTTS = model.IsTextToSpeech; | IsTTS = model.IsTextToSpeech; | ||||
| Timestamp = model.Timestamp; | Timestamp = model.Timestamp; | ||||
| EditedTimestamp = model.EditedTimestamp; | EditedTimestamp = model.EditedTimestamp; | ||||
| @@ -80,38 +84,32 @@ namespace Discord.Rest | |||||
| else | else | ||||
| Embeds = Array.Empty<Embed>(); | Embeds = Array.Empty<Embed>(); | ||||
| if (model.Mentions.Length > 0) | |||||
| if (guildChannel != null && model.Mentions.Length > 0) | |||||
| { | { | ||||
| var discord = Discord; | |||||
| var builder = ImmutableArray.CreateBuilder<PublicUser>(model.Mentions.Length); | |||||
| var mentions = new PublicUser[model.Mentions.Length]; | |||||
| for (int i = 0; i < model.Mentions.Length; i++) | for (int i = 0; i < model.Mentions.Length; i++) | ||||
| builder.Add(new PublicUser(discord, model.Mentions[i])); | |||||
| MentionedUsers = builder.ToArray(); | |||||
| mentions[i] = new PublicUser(discord, model.Mentions[i]); | |||||
| MentionedUsers = ImmutableArray.Create(mentions); | |||||
| } | } | ||||
| else | else | ||||
| MentionedUsers = Array.Empty<PublicUser>(); | MentionedUsers = Array.Empty<PublicUser>(); | ||||
| MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); | |||||
| MentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); | |||||
| if (model.IsMentioningEveryone) | |||||
| if (guildChannel != null) | |||||
| { | { | ||||
| ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; | |||||
| if (guildId != null) | |||||
| { | |||||
| if (MentionedRoleIds.Count == 0) | |||||
| MentionedRoleIds = ImmutableArray.Create(guildId.Value); | |||||
| else | |||||
| { | |||||
| var builder = ImmutableArray.CreateBuilder<ulong>(MentionedRoleIds.Count + 1); | |||||
| builder.AddRange(MentionedRoleIds); | |||||
| builder.Add(guildId.Value); | |||||
| MentionedRoleIds = builder.ToImmutable(); | |||||
| } | |||||
| } | |||||
| MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); | |||||
| var mentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); | |||||
| if (model.IsMentioningEveryone) | |||||
| mentionedRoleIds = mentionedRoleIds.Add(guildChannel.Guild.EveryoneRole.Id); | |||||
| MentionedRoleIds = mentionedRoleIds; | |||||
| } | |||||
| else | |||||
| { | |||||
| MentionedChannelIds = Array.Empty<ulong>(); | |||||
| MentionedRoleIds = Array.Empty<ulong>(); | |||||
| } | } | ||||
| Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); | Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); | ||||
| Author.Update(model.Author); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -125,9 +123,9 @@ namespace Discord.Rest | |||||
| Model model; | Model model; | ||||
| if (guildChannel != null) | if (guildChannel != null) | ||||
| model = await Discord.APIClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); | |||||
| model = await Discord.ApiClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); | |||||
| else | else | ||||
| model = await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); | |||||
| model = await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -136,18 +134,15 @@ namespace Discord.Rest | |||||
| { | { | ||||
| var guildChannel = Channel as GuildChannel; | var guildChannel = Channel as GuildChannel; | ||||
| if (guildChannel != null) | if (guildChannel != null) | ||||
| await Discord.APIClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); | |||||
| else | else | ||||
| await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| public override string ToString() => Text; | public override string ToString() => Text; | ||||
| private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | ||||
| IUser IMessage.Author => Author; | IUser IMessage.Author => Author; | ||||
| IReadOnlyList<Attachment> IMessage.Attachments => Attachments; | |||||
| IReadOnlyList<Embed> IMessage.Embeds => Embeds; | |||||
| IReadOnlyList<ulong> IMessage.MentionedChannelIds => MentionedChannelIds; | |||||
| IReadOnlyList<IUser> IMessage.MentionedUsers => MentionedUsers; | IReadOnlyList<IUser> IMessage.MentionedUsers => MentionedUsers; | ||||
| } | } | ||||
| } | } | ||||
| @@ -60,12 +60,12 @@ namespace Discord.Rest | |||||
| var args = new ModifyGuildRoleParams(); | var args = new ModifyGuildRoleParams(); | ||||
| func(args); | func(args); | ||||
| var response = await Discord.APIClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| var response = await Discord.ApiClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| Update(response); | Update(response); | ||||
| } | } | ||||
| /// <summary> Deletes this message. </summary> | /// <summary> Deletes this message. </summary> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| => await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); | |||||
| => await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| @@ -75,8 +75,8 @@ namespace Discord.Rest | |||||
| async Task<IEnumerable<IGuildUser>> IRole.GetUsers() | async Task<IEnumerable<IGuildUser>> IRole.GetUsers() | ||||
| { | { | ||||
| //A tad hacky, but it works | |||||
| var models = await Discord.APIClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); | |||||
| //TODO: Rethink this, it isn't paginated or anything... | |||||
| var models = await Discord.ApiClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); | |||||
| return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); | return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,20 +0,0 @@ | |||||
| using Model = Discord.API.User; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public class DMUser : User, IDMUser | |||||
| { | |||||
| /// <inheritdoc /> | |||||
| public DMChannel Channel { get; } | |||||
| internal override DiscordClient Discord => Channel.Discord; | |||||
| internal DMUser(DMChannel channel, Model model) | |||||
| : base(model) | |||||
| { | |||||
| Channel = channel; | |||||
| } | |||||
| IDMChannel IDMUser.Channel => Channel; | |||||
| } | |||||
| } | |||||
| @@ -55,23 +55,13 @@ namespace Discord.Rest | |||||
| public async Task Update() | public async Task Update() | ||||
| { | { | ||||
| var model = await Discord.APIClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| public bool HasRole(IRole role) | |||||
| { | |||||
| for (int i = 0; i < _roles.Length; i++) | |||||
| { | |||||
| if (_roles[i].Id == role.Id) | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public async Task Kick() | public async Task Kick() | ||||
| { | { | ||||
| await Discord.APIClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| public ChannelPermissions GetPermissions(IGuildChannel channel) | public ChannelPermissions GetPermissions(IGuildChannel channel) | ||||
| @@ -91,13 +81,13 @@ namespace Discord.Rest | |||||
| if (isCurrentUser && args.Nickname.IsSpecified) | if (isCurrentUser && args.Nickname.IsSpecified) | ||||
| { | { | ||||
| var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; | var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; | ||||
| await Discord.APIClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); | |||||
| args.Nickname = new API.Optional<string>(); //Remove | args.Nickname = new API.Optional<string>(); //Remove | ||||
| } | } | ||||
| if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) | if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) | ||||
| { | { | ||||
| await Discord.APIClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| if (args.Deaf.IsSpecified) | if (args.Deaf.IsSpecified) | ||||
| IsDeaf = args.Deaf.Value; | IsDeaf = args.Deaf.Value; | ||||
| if (args.Mute.IsSpecified) | if (args.Mute.IsSpecified) | ||||
| @@ -30,7 +30,7 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Update() | public async Task Update() | ||||
| { | { | ||||
| var model = await Discord.APIClient.GetCurrentUser().ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| @@ -41,7 +41,7 @@ namespace Discord.Rest | |||||
| var args = new ModifyCurrentUserParams(); | var args = new ModifyCurrentUserParams(); | ||||
| func(args); | func(args); | ||||
| var model = await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| } | } | ||||
| @@ -45,10 +45,10 @@ namespace Discord.Rest | |||||
| Username = model.Username; | Username = model.Username; | ||||
| } | } | ||||
| public async Task<DMChannel> CreateDMChannel() | |||||
| protected virtual async Task<DMChannel> CreateDMChannelInternal() | |||||
| { | { | ||||
| var args = new CreateDMChannelParams { RecipientId = Id }; | var args = new CreateDMChannelParams { RecipientId = Id }; | ||||
| var model = await Discord.APIClient.CreateDMChannel(args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateDMChannel(args).ConfigureAwait(false); | |||||
| return new DMChannel(Discord, model); | return new DMChannel(Discord, model); | ||||
| } | } | ||||
| @@ -63,6 +63,6 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IDMChannel> IUser.CreateDMChannel() | async Task<IDMChannel> IUser.CreateDMChannel() | ||||
| => await CreateDMChannel().ConfigureAwait(false); | |||||
| => await CreateDMChannelInternal().ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,139 +1,305 @@ | |||||
| using System; | |||||
| using Discord.API.Rest; | |||||
| using Discord.Logging; | |||||
| using Discord.Net.Rest; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | |||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.API; | |||||
| using Discord.Net.Rest; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| public class DiscordClient : IDiscordClient | |||||
| //TODO: Docstrings | |||||
| //TODO: Log Logins/Logouts | |||||
| public sealed class DiscordClient : IDiscordClient, IDisposable | |||||
| { | { | ||||
| internal int MessageCacheSize { get; } = 100; | |||||
| public event EventHandler<LogMessageEventArgs> Log; | |||||
| public event EventHandler LoggedIn, LoggedOut; | |||||
| public event EventHandler Connected, Disconnected; | |||||
| public event EventHandler<VoiceChannelEventArgs> VoiceConnected, VoiceDisconnected; | |||||
| public event EventHandler<ChannelEventArgs> ChannelCreated, ChannelDestroyed; | |||||
| public event EventHandler<ChannelUpdatedEventArgs> ChannelUpdated; | |||||
| public event EventHandler<MessageEventArgs> MessageReceived, MessageDeleted; | |||||
| public event EventHandler<MessageUpdatedEventArgs> MessageUpdated; | |||||
| public event EventHandler<RoleEventArgs> RoleCreated, RoleDeleted; | |||||
| public event EventHandler<RoleUpdatedEventArgs> RoleUpdated; | |||||
| public event EventHandler<GuildEventArgs> JoinedGuild, LeftGuild; | |||||
| public event EventHandler<GuildEventArgs> GuildAvailable, GuildUnavailable; | |||||
| public event EventHandler<GuildUpdatedEventArgs> GuildUpdated; | |||||
| public event EventHandler<CurrentUserUpdatedEventArgs> CurrentUserUpdated; | |||||
| public event EventHandler<UserEventArgs> UserJoined, UserLeft; | |||||
| public event EventHandler<UserEventArgs> UserBanned, UserUnbanned; | |||||
| public event EventHandler<UserUpdatedEventArgs> UserUpdated; | |||||
| public event EventHandler<TypingEventArgs> UserIsTyping; | |||||
| private readonly Logger _discordLogger, _gatewayLogger; | |||||
| private readonly SemaphoreSlim _connectionLock; | |||||
| private readonly RestClientProvider _restClientProvider; | |||||
| private readonly LogManager _log; | |||||
| private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | |||||
| private readonly bool _enablePreUpdateEvents; | |||||
| private readonly int _largeThreshold; | |||||
| private readonly int _totalShards; | |||||
| private IReadOnlyDictionary<string, VoiceRegion> _voiceRegions; | |||||
| private CancellationTokenSource _cancelTokenSource; | |||||
| private bool _isDisposed; | |||||
| private SelfUser _currentUser; | |||||
| private ConcurrentDictionary<ulong, Guild> _guilds; | |||||
| private ConcurrentDictionary<ulong, IChannel> _channels; | |||||
| private ConcurrentDictionary<ulong, DMChannel> _dmChannels; //Key = RecipientId | |||||
| private ConcurrentDictionary<ulong, User> _users; | |||||
| public int ShardId { get; } | |||||
| public bool IsLoggedIn { get; private set; } | |||||
| public API.DiscordApiClient ApiClient { get; private set; } | |||||
| public SelfUser CurrentUser { get; private set; } | |||||
| //public GatewaySocket GatewaySocket { get; private set; } | |||||
| internal int MessageCacheSize { get; private set; } | |||||
| internal bool UsePermissionCache { get; private set; } | |||||
| public TokenType AuthTokenType => ApiClient.AuthTokenType; | |||||
| public IRestClient RestClient => ApiClient.RestClient; | |||||
| public IRequestQueue RequestQueue => ApiClient.RequestQueue; | |||||
| public IEnumerable<Guild> Guilds => _guilds.Values; | |||||
| public IEnumerable<IChannel> Channels => _channels.Values; | |||||
| public IEnumerable<DMChannel> DMChannels => _dmChannels.Values; | |||||
| public IEnumerable<VoiceRegion> VoiceRegions => _voiceRegions.Values; | |||||
| public SelfUser CurrentUser | |||||
| //public bool IsConnected => GatewaySocket.State == ConnectionState.Connected; | |||||
| public DiscordClient(DiscordSocketConfig config = null) | |||||
| { | { | ||||
| get | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| if (config == null) | |||||
| config = new DiscordSocketConfig(); | |||||
| _restClientProvider = config.RestClientProvider; | |||||
| ShardId = config.ShardId; | |||||
| _totalShards = config.TotalShards; | |||||
| _connectionTimeout = config.ConnectionTimeout; | |||||
| _reconnectDelay = config.ReconnectDelay; | |||||
| _failedReconnectDelay = config.FailedReconnectDelay; | |||||
| MessageCacheSize = config.MessageCacheSize; | |||||
| UsePermissionCache = config.UsePermissionsCache; | |||||
| _enablePreUpdateEvents = config.EnablePreUpdateEvents; | |||||
| _largeThreshold = config.LargeThreshold; | |||||
| _log = new LogManager(config.LogLevel); | |||||
| _log.Message += (s, e) => Log.Raise(this, e); | |||||
| _discordLogger = _log.CreateLogger("Discord"); | |||||
| _gatewayLogger = _log.CreateLogger("Gateway"); | |||||
| _connectionLock = new SemaphoreSlim(1, 1); | |||||
| ApiClient = new API.DiscordApiClient(_restClientProvider); | |||||
| ApiClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); | |||||
| _channels = new ConcurrentDictionary<ulong, IChannel>(1, 100); | |||||
| _dmChannels = new ConcurrentDictionary<ulong, DMChannel>(1, 100); | |||||
| _guilds = new ConcurrentDictionary<ulong, Guild>(1, 25); | |||||
| _users = new ConcurrentDictionary<ulong, User>(1, 250); | |||||
| } | } | ||||
| public TokenType AuthTokenType | |||||
| public async Task Login(string email, string password) | |||||
| { | { | ||||
| get | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| await LoginInternal(email, password).ConfigureAwait(false); | |||||
| } | } | ||||
| finally { _connectionLock.Release(); } | |||||
| } | } | ||||
| public DiscordAPIClient APIClient | |||||
| public async Task Login(TokenType tokenType, string token, bool validateToken = true) | |||||
| { | { | ||||
| get | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false); | |||||
| } | } | ||||
| finally { _connectionLock.Release(); } | |||||
| } | } | ||||
| public IRequestQueue RequestQueue | |||||
| private async Task LoginInternal(string email, string password) | |||||
| { | { | ||||
| get | |||||
| if (IsLoggedIn) | |||||
| await LogoutInternal().ConfigureAwait(false); | |||||
| try | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| var args = new LoginParams { Email = email, Password = password }; | |||||
| await ApiClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); | |||||
| await CompleteLogin(false).ConfigureAwait(false); | |||||
| } | } | ||||
| catch { await LogoutInternal().ConfigureAwait(false); throw; } | |||||
| } | } | ||||
| public IRestClient RestClient | |||||
| private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) | |||||
| { | { | ||||
| get | |||||
| if (IsLoggedIn) | |||||
| await LogoutInternal().ConfigureAwait(false); | |||||
| try | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| await ApiClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); | |||||
| await CompleteLogin(validateToken).ConfigureAwait(false); | |||||
| } | } | ||||
| catch { await LogoutInternal().ConfigureAwait(false); throw; } | |||||
| } | } | ||||
| public Task<IGuild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) | |||||
| private async Task CompleteLogin(bool validateToken) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| } | |||||
| if (validateToken) | |||||
| { | |||||
| try | |||||
| { | |||||
| await ApiClient.ValidateToken().ConfigureAwait(false); | |||||
| var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); | |||||
| _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); | |||||
| public Task<IChannel> GetChannel(ulong id) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| } | |||||
| catch { await ApiClient.Logout().ConfigureAwait(false); } | |||||
| } | |||||
| public Task<IEnumerable<IConnection>> GetConnections() | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| IsLoggedIn = true; | |||||
| LoggedIn.Raise(this); | |||||
| } | } | ||||
| public Task<ISelfUser> GetCurrentUser() | |||||
| public async Task Logout() | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| _cancelTokenSource?.Cancel(); | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await LogoutInternal().ConfigureAwait(false); | |||||
| } | |||||
| finally { _connectionLock.Release(); } | |||||
| } | } | ||||
| public Task<IEnumerable<IDMChannel>> GetDMChannels() | |||||
| private async Task LogoutInternal() | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| } | |||||
| bool wasLoggedIn = IsLoggedIn; | |||||
| public Task<IGuild> GetGuild(ulong id) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| if (_cancelTokenSource != null) | |||||
| { | |||||
| try { _cancelTokenSource.Cancel(false); } | |||||
| catch { } | |||||
| } | |||||
| public Task<IEnumerable<IUserGuild>> GetGuilds() | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| await ApiClient.Logout().ConfigureAwait(false); | |||||
| _channels.Clear(); | |||||
| _dmChannels.Clear(); | |||||
| _guilds.Clear(); | |||||
| _users.Clear(); | |||||
| _currentUser = null; | |||||
| if (wasLoggedIn) | |||||
| { | |||||
| IsLoggedIn = false; | |||||
| LoggedOut.Raise(this); | |||||
| } | |||||
| } | } | ||||
| public Task<IInvite> GetInvite(string inviteIdOrXkcd) | |||||
| public async Task<IEnumerable<Connection>> GetConnections() | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| var models = await ApiClient.GetCurrentUserConnections().ConfigureAwait(false); | |||||
| return models.Select(x => new Connection(x)); | |||||
| } | } | ||||
| public Task<IVoiceRegion> GetOptimalVoiceRegion() | |||||
| public IChannel GetChannel(ulong id) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| IChannel channel; | |||||
| if (_channels.TryGetValue(id, out channel)) | |||||
| return channel; | |||||
| return null; | |||||
| } | } | ||||
| public Task<IUser> GetUser(ulong id) | |||||
| public async Task<Invite> GetInvite(string inviteIdOrXkcd) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| var model = await ApiClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return new Invite(this, model); | |||||
| return null; | |||||
| } | } | ||||
| public Task<IUser> GetUser(string username, ushort discriminator) | |||||
| public Guild GetGuild(ulong id) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| Guild guild; | |||||
| if (_guilds.TryGetValue(id, out guild)) | |||||
| return guild; | |||||
| return null; | |||||
| } | } | ||||
| public Task<IVoiceRegion> GetVoiceRegion(string id) | |||||
| public async Task<Guild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| var args = new CreateGuildParams(); | |||||
| var model = await ApiClient.CreateGuild(args).ConfigureAwait(false); | |||||
| return new Guild(this, model); | |||||
| } | } | ||||
| public Task<IEnumerable<IVoiceRegion>> GetVoiceRegions() | |||||
| public User GetUser(ulong id) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| User user; | |||||
| if (_users.TryGetValue(id, out user)) | |||||
| return user; | |||||
| return null; | |||||
| } | } | ||||
| public Task Login(string email, string password) | |||||
| public User GetUser(string username, ushort discriminator) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| return _users.Where(x => x.Value.Discriminator == discriminator && x.Value.Username == username).Select(x => x.Value).FirstOrDefault(); | |||||
| } | } | ||||
| public Task Login(TokenType tokenType, string token, bool validateToken = true) | |||||
| public async Task<IEnumerable<User>> QueryUsers(string query, int limit) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| var models = await ApiClient.QueryUsers(query, limit).ConfigureAwait(false); | |||||
| return models.Select(x => new User(this, x)); | |||||
| } | } | ||||
| public Task Logout() | |||||
| public VoiceRegion GetVoiceRegion(string id) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| VoiceRegion region; | |||||
| if (_voiceRegions.TryGetValue(id, out region)) | |||||
| return region; | |||||
| return null; | |||||
| } | } | ||||
| public Task<IEnumerable<IUser>> QueryUsers(string query, int limit) | |||||
| void Dispose(bool disposing) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| _cancelTokenSource.Dispose(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | } | ||||
| public void Dispose() => Dispose(true); | |||||
| API.DiscordApiClient IDiscordClient.ApiClient => ApiClient; | |||||
| Task<IChannel> IDiscordClient.GetChannel(ulong id) | |||||
| => Task.FromResult(GetChannel(id)); | |||||
| Task<IEnumerable<IDMChannel>> IDiscordClient.GetDMChannels() | |||||
| => Task.FromResult<IEnumerable<IDMChannel>>(DMChannels); | |||||
| async Task<IEnumerable<IConnection>> IDiscordClient.GetConnections() | |||||
| => await GetConnections().ConfigureAwait(false); | |||||
| async Task<IInvite> IDiscordClient.GetInvite(string inviteIdOrXkcd) | |||||
| => await GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||||
| Task<IGuild> IDiscordClient.GetGuild(ulong id) | |||||
| => Task.FromResult<IGuild>(GetGuild(id)); | |||||
| Task<IEnumerable<IUserGuild>> IDiscordClient.GetGuilds() | |||||
| => Task.FromResult<IEnumerable<IUserGuild>>(Guilds); | |||||
| async Task<IGuild> IDiscordClient.CreateGuild(string name, IVoiceRegion region, Stream jpegIcon) | |||||
| => await CreateGuild(name, region, jpegIcon).ConfigureAwait(false); | |||||
| Task<IUser> IDiscordClient.GetUser(ulong id) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | |||||
| Task<IUser> IDiscordClient.GetUser(string username, ushort discriminator) | |||||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||||
| Task<ISelfUser> IDiscordClient.GetCurrentUser() | |||||
| => Task.FromResult<ISelfUser>(CurrentUser); | |||||
| async Task<IEnumerable<IUser>> IDiscordClient.QueryUsers(string query, int limit) | |||||
| => await QueryUsers(query, limit).ConfigureAwait(false); | |||||
| Task<IEnumerable<IVoiceRegion>> IDiscordClient.GetVoiceRegions() | |||||
| => Task.FromResult<IEnumerable<IVoiceRegion>>(VoiceRegions); | |||||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegion(string id) | |||||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,39 @@ | |||||
| using Discord.Net.WebSockets; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class DiscordSocketConfig : DiscordConfig | |||||
| { | |||||
| /// <summary> Gets or sets the id for this shard. Must be less than TotalShards. </summary> | |||||
| public int ShardId { get; set; } = 0; | |||||
| /// <summary> Gets or sets the total number of shards for this application. </summary> | |||||
| public int TotalShards { get; set; } = 1; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary> | |||||
| public int ConnectionTimeout { get; set; } = 30000; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary> | |||||
| public int ReconnectDelay { get; set; } = 1000; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | |||||
| public int FailedReconnectDelay { get; set; } = 15000; | |||||
| /// <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; } = 100; | |||||
| /// <summary> | |||||
| /// Gets or sets whether the permissions cache should be used. | |||||
| /// This makes operations such as User.GetPermissions(Channel), User.GuildPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage. | |||||
| /// </summary> | |||||
| public bool UsePermissionsCache { get; set; } = true; | |||||
| /// <summary> Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. </summary> | |||||
| public bool EnablePreUpdateEvents { get; set; } = true; | |||||
| /// <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. | |||||
| /// Decreasing this may reduce CPU usage while increasing login time and network usage. | |||||
| /// </summary> | |||||
| public int LargeThreshold { get; set; } = 250; | |||||
| //Engines | |||||
| /// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | |||||
| public WebSocketProvider WebSocketProvider { get; set; } = null; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| //TODO: Look into Internal abstract pattern - can we get rid of this? | |||||
| public abstract class Channel : IChannel | |||||
| { | |||||
| /// <inheritdoc /> | |||||
| public ulong Id { get; private set; } | |||||
| public IEnumerable<User> Users => GetUsersInternal(); | |||||
| /// <inheritdoc /> | |||||
| public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||||
| internal Channel(ulong id) | |||||
| { | |||||
| Id = id; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public User GetUser(ulong id) | |||||
| => GetUserInternal(id); | |||||
| protected abstract User GetUserInternal(ulong id); | |||||
| protected abstract IEnumerable<User> GetUsersInternal(); | |||||
| Task<IEnumerable<IUser>> IChannel.GetUsers() | |||||
| => Task.FromResult<IEnumerable<IUser>>(GetUsersInternal()); | |||||
| Task<IEnumerable<IUser>> IChannel.GetUsers(int limit, int offset) | |||||
| => Task.FromResult<IEnumerable<IUser>>(GetUsersInternal().Skip(offset).Take(limit)); | |||||
| Task<IUser> IChannel.GetUser(ulong id) | |||||
| => Task.FromResult<IUser>(GetUserInternal(id)); | |||||
| } | |||||
| } | |||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -11,41 +10,34 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class DMChannel : IDMChannel | |||||
| public class DMChannel : Channel, IDMChannel | |||||
| { | { | ||||
| private readonly MessageCache _messages; | private readonly MessageCache _messages; | ||||
| /// <inheritdoc /> | |||||
| public ulong Id { get; } | |||||
| internal DiscordClient Discord { get; } | internal DiscordClient Discord { get; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DMUser Recipient { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||||
| public User Recipient { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IEnumerable<IUser> Users => ImmutableArray.Create<IUser>(Discord.CurrentUser, Recipient); | |||||
| public new IEnumerable<User> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||||
| public IEnumerable<Message> CachedMessages => _messages.Messages; | public IEnumerable<Message> CachedMessages => _messages.Messages; | ||||
| internal DMChannel(DiscordClient discord, Model model) | |||||
| internal DMChannel(DiscordClient discord, User recipient, Model model) | |||||
| : base(model.Id) | |||||
| { | { | ||||
| Id = model.Id; | |||||
| Discord = discord; | Discord = discord; | ||||
| Recipient = recipient; | |||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| private void Update(Model model) | private void Update(Model model) | ||||
| { | { | ||||
| if (Recipient == null) | |||||
| Recipient = new DMUser(this, model.Recipient); | |||||
| else | |||||
| Recipient.Update(model.Recipient); | |||||
| Recipient.Update(model.Recipient); | |||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public IUser GetUser(ulong id) | |||||
| protected override User GetUserInternal(ulong id) | |||||
| { | { | ||||
| if (id == Recipient.Id) | if (id == Recipient.Id) | ||||
| return Recipient; | return Recipient; | ||||
| @@ -54,6 +46,10 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| return null; | return null; | ||||
| } | } | ||||
| protected override IEnumerable<User> GetUsersInternal() | |||||
| { | |||||
| return Users; | |||||
| } | |||||
| /// <summary> Gets the message from this channel's cache with the given id, or null if none was found. </summary> | /// <summary> Gets the message from this channel's cache with the given id, or null if none was found. </summary> | ||||
| public Message GetCachedMessage(ulong id) | public Message GetCachedMessage(ulong id) | ||||
| @@ -75,8 +71,8 @@ namespace Discord.WebSocket | |||||
| public async Task<Message> SendMessage(string text, bool isTTS = false) | public async Task<Message> SendMessage(string text, bool isTTS = false) | ||||
| { | { | ||||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.CreateDMMessage(Id, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | |||||
| var model = await Discord.ApiClient.CreateDMMessage(Id, args).ConfigureAwait(false); | |||||
| return new Message(this, GetUser(model.Id), model); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<Message> SendFile(string filePath, string text = null, bool isTTS = false) | public async Task<Message> SendFile(string filePath, string text = null, bool isTTS = false) | ||||
| @@ -85,49 +81,49 @@ namespace Discord.WebSocket | |||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadDMFile(Id, file, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | |||||
| var model = await Discord.ApiClient.UploadDMFile(Id, file, args).ConfigureAwait(false); | |||||
| return new Message(this, GetUser(model.Id), model); | |||||
| } | } | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | |||||
| var model = await Discord.ApiClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); | |||||
| return new Message(this, GetUser(model.Id), model); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task DeleteMessages(IEnumerable<IMessage> messages) | public async Task DeleteMessages(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task TriggerTyping() | public async Task TriggerTyping() | ||||
| { | { | ||||
| await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Close() | public async Task Close() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override string ToString() => '@' + Recipient.ToString(); | public override string ToString() => '@' + Recipient.ToString(); | ||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | ||||
| IDMUser IDMChannel.Recipient => Recipient; | |||||
| IUser IDMChannel.Recipient => Recipient; | |||||
| IEnumerable<IMessage> IMessageChannel.CachedMessages => CachedMessages; | IEnumerable<IMessage> IMessageChannel.CachedMessages => CachedMessages; | ||||
| Task<IEnumerable<IUser>> IChannel.GetUsers() | Task<IEnumerable<IUser>> IChannel.GetUsers() | ||||
| => Task.FromResult(Users); | |||||
| => Task.FromResult<IEnumerable<IUser>>(Users); | |||||
| Task<IEnumerable<IUser>> IChannel.GetUsers(int limit, int offset) | Task<IEnumerable<IUser>> IChannel.GetUsers(int limit, int offset) | ||||
| => Task.FromResult(Users.Skip(offset).Take(limit)); | |||||
| => Task.FromResult<IEnumerable<IUser>>(Users.Skip(offset).Take(limit)); | |||||
| Task<IUser> IChannel.GetUser(ulong id) | Task<IUser> IChannel.GetUser(ulong id) | ||||
| => Task.FromResult(GetUser(id)); | |||||
| => Task.FromResult<IUser>(GetUser(id)); | |||||
| Task<IMessage> IMessageChannel.GetCachedMessage(ulong id) | Task<IMessage> IMessageChannel.GetCachedMessage(ulong id) | ||||
| => Task.FromResult<IMessage>(GetCachedMessage(id)); | => Task.FromResult<IMessage>(GetCachedMessage(id)); | ||||
| async Task<IEnumerable<IMessage>> IMessageChannel.GetMessages(int limit) | async Task<IEnumerable<IMessage>> IMessageChannel.GetMessages(int limit) | ||||
| @@ -8,13 +8,11 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| public abstract class GuildChannel : IGuildChannel | |||||
| public abstract class GuildChannel : Channel, IGuildChannel | |||||
| { | { | ||||
| private ConcurrentDictionary<ulong, Overwrite> _overwrites; | private ConcurrentDictionary<ulong, Overwrite> _overwrites; | ||||
| internal PermissionsCache _permissions; | internal PermissionsCache _permissions; | ||||
| /// <inheritdoc /> | |||||
| public ulong Id { get; } | |||||
| /// <summary> Gets the guild this channel is a member of. </summary> | /// <summary> Gets the guild this channel is a member of. </summary> | ||||
| public Guild Guild { get; } | public Guild Guild { get; } | ||||
| @@ -22,17 +20,15 @@ namespace Discord.WebSocket | |||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| public abstract IEnumerable<GuildUser> Users { get; } | |||||
| /// <inheritdoc /> | |||||
| public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||||
| public new abstract IEnumerable<GuildUser> Users { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyDictionary<ulong, Overwrite> PermissionOverwrites => _overwrites; | public IReadOnlyDictionary<ulong, Overwrite> PermissionOverwrites => _overwrites; | ||||
| internal DiscordClient Discord => Guild.Discord; | internal DiscordClient Discord => Guild.Discord; | ||||
| internal GuildChannel(Guild guild, Model model) | internal GuildChannel(Guild guild, Model model) | ||||
| : base(model.Id) | |||||
| { | { | ||||
| Id = model.Id; | |||||
| Guild = guild; | Guild = guild; | ||||
| Update(model); | Update(model); | ||||
| @@ -57,11 +53,19 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyGuildChannelParams(); | var args = new ModifyGuildChannelParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> Gets a user in this channel with the given id. </summary> | /// <summary> Gets a user in this channel with the given id. </summary> | ||||
| public abstract GuildUser GetUser(ulong id); | |||||
| public new abstract GuildUser GetUser(ulong id); | |||||
| protected override User GetUserInternal(ulong id) | |||||
| { | |||||
| return GetUser(id).GlobalUser; | |||||
| } | |||||
| protected override IEnumerable<User> GetUsersInternal() | |||||
| { | |||||
| return Users.Select(x => x.GlobalUser); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public OverwritePermissions? GetPermissionOverwrite(IUser user) | public OverwritePermissions? GetPermissionOverwrite(IUser user) | ||||
| @@ -82,7 +86,7 @@ namespace Discord.WebSocket | |||||
| /// <summary> Downloads a collection of all invites to this channel. </summary> | /// <summary> Downloads a collection of all invites to this channel. </summary> | ||||
| public async Task<IEnumerable<InviteMetadata>> GetInvites() | public async Task<IEnumerable<InviteMetadata>> GetInvites() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetChannelInvites(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetChannelInvites(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new InviteMetadata(Discord, x)); | return models.Select(x => new InviteMetadata(Discord, x)); | ||||
| } | } | ||||
| @@ -90,23 +94,23 @@ namespace Discord.WebSocket | |||||
| public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) | public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | ||||
| await Discord.APIClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) | public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; | ||||
| await Discord.APIClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task RemovePermissionOverwrite(IUser user) | public async Task RemovePermissionOverwrite(IUser user) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task RemovePermissionOverwrite(IRole role) | public async Task RemovePermissionOverwrite(IRole role) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> Creates a new invite to this channel. </summary> | /// <summary> Creates a new invite to this channel. </summary> | ||||
| @@ -123,14 +127,14 @@ namespace Discord.WebSocket | |||||
| Temporary = isTemporary, | Temporary = isTemporary, | ||||
| XkcdPass = withXkcd | XkcdPass = withXkcd | ||||
| }; | }; | ||||
| var model = await Discord.APIClient.CreateChannelInvite(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateChannelInvite(Id, args).ConfigureAwait(false); | |||||
| return new InviteMetadata(Discord, model); | return new InviteMetadata(Discord, model); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -42,7 +42,7 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyTextChannelParams(); | var args = new ModifyTextChannelParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> Gets the message from this channel's cache with the given id, or null if none was found. </summary> | /// <summary> Gets the message from this channel's cache with the given id, or null if none was found. </summary> | ||||
| @@ -73,8 +73,8 @@ namespace Discord.WebSocket | |||||
| public async Task<Message> SendMessage(string text, bool isTTS = false) | public async Task<Message> SendMessage(string text, bool isTTS = false) | ||||
| { | { | ||||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | |||||
| var model = await Discord.ApiClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| return new Message(this, GetUser(model.Id), model); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<Message> SendFile(string filePath, string text = null, bool isTTS = false) | public async Task<Message> SendFile(string filePath, string text = null, bool isTTS = false) | ||||
| @@ -83,28 +83,28 @@ namespace Discord.WebSocket | |||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | |||||
| var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); | |||||
| return new Message(this, GetUser(model.Author.Id), model); | |||||
| } | } | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | public async Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) | ||||
| { | { | ||||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.APIClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); | |||||
| return new Message(this, model); | |||||
| var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); | |||||
| return new Message(this, GetUser(model.Author.Id), model); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task DeleteMessages(IEnumerable<IMessage> messages) | public async Task DeleteMessages(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| await Discord.APIClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task TriggerTyping() | public async Task TriggerTyping() | ||||
| { | { | ||||
| await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | ||||
| @@ -34,7 +34,7 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyVoiceChannelParams(); | var args = new ModifyVoiceChannelParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| public override GuildUser GetUser(ulong id) | public override GuildUser GetUser(ulong id) | ||||
| @@ -6,18 +6,21 @@ using System.Collections.Immutable; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Guild; | using Model = Discord.API.Guild; | ||||
| using EmbedModel = Discord.API.GuildEmbed; | |||||
| using RoleModel = Discord.API.Role; | |||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> Represents a Discord guild (called a server in the official client). </summary> | /// <summary> Represents a Discord guild (called a server in the official client). </summary> | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class Guild : IGuild | |||||
| public class Guild : IGuild, IUserGuild | |||||
| { | { | ||||
| private ConcurrentDictionary<ulong, GuildChannel> _channels; | |||||
| private ConcurrentDictionary<ulong, GuildUser> _members; | |||||
| private ConcurrentDictionary<ulong, Role> _roles; | private ConcurrentDictionary<ulong, Role> _roles; | ||||
| private ulong _ownerId; | |||||
| private ulong? _afkChannelId, _embedChannelId; | |||||
| private string _iconId, _splashId; | private string _iconId, _splashId; | ||||
| private int _userCount; | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| @@ -31,34 +34,52 @@ namespace Discord.WebSocket | |||||
| public bool IsEmbeddable { get; private set; } | public bool IsEmbeddable { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public int VerificationLevel { get; private set; } | public int VerificationLevel { get; private set; } | ||||
| public int UserCount { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public ulong? AFKChannelId { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public ulong? EmbedChannelId { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public ulong OwnerId { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string VoiceRegionId { get; private set; } | |||||
| public VoiceRegion VoiceRegion { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<Emoji> Emojis { get; private set; } | public IReadOnlyList<Emoji> Emojis { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<string> Features { get; private set; } | public IReadOnlyList<string> Features { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); | public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, _splashId); | public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, _splashId); | ||||
| /// <inheritdoc /> | |||||
| public ulong DefaultChannelId => Id; | |||||
| /// <inheritdoc /> | |||||
| /// <summary> Gets the number of channels in this guild. </summary> | |||||
| public int ChannelCount => _channels.Count; | |||||
| /// <summary> Gets the number of roles in this guild. </summary> | |||||
| public int RoleCount => _roles.Count; | |||||
| /// <summary> Gets the number of users in this guild. </summary> | |||||
| public int UserCount => _userCount; | |||||
| /// <summary> Gets the number of users downloaded for this guild so far. </summary> | |||||
| internal int CurrentUserCount => _members.Count; | |||||
| /// <summary> Gets the the role representing all users in a guild. </summary> | |||||
| public Role EveryoneRole => GetRole(Id); | public Role EveryoneRole => GetRole(Id); | ||||
| public GuildUser CurrentUser => GetUser(Discord.CurrentUser.Id); | |||||
| /// <summary> Gets the user that created this guild. </summary> | |||||
| public GuildUser Owner => GetUser(_ownerId); | |||||
| /// <summary> Gets the default channel for this guild. </summary> | |||||
| public TextChannel DefaultChannel => GetChannel(Id) as TextChannel; | |||||
| /// <summary> Gets the AFK voice channel for this guild. </summary> | |||||
| public VoiceChannel AFKChannel => GetChannel(_afkChannelId.GetValueOrDefault(0)) as VoiceChannel; | |||||
| /// <summary> Gets the embed channel for this guild. </summary> | |||||
| public IChannel EmbedChannel => GetChannel(_embedChannelId.GetValueOrDefault(0)); //TODO: Is this text or voice? | |||||
| /// <summary> Gets a collection of all channels in this guild. </summary> | |||||
| public IEnumerable<GuildChannel> Channels => _channels.Select(x => x.Value); | |||||
| /// <summary> Gets a collection of text channels in this guild. </summary> | |||||
| public IEnumerable<TextChannel> TextChannels => _channels.Select(x => x.Value).OfType<TextChannel>(); | |||||
| /// <summary> Gets a collection of voice channels in this guild. </summary> | |||||
| public IEnumerable<VoiceChannel> VoiceChannels => _channels.Select(x => x.Value).OfType<VoiceChannel>(); | |||||
| /// <summary> Gets a collection of all roles in this guild. </summary> | /// <summary> Gets a collection of all roles in this guild. </summary> | ||||
| public IEnumerable<Role> Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty<Role>(); | public IEnumerable<Role> Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty<Role>(); | ||||
| public IEnumerable<GuildUser> Users => Array.Empty<GuildUser>(); | |||||
| /// <summary> Gets a collection of all users in this guild. </summary> | |||||
| public IEnumerable<GuildUser> Users => _members.Select(x => x.Value); | |||||
| internal Guild(DiscordClient discord, Model model) | internal Guild(DiscordClient discord, Model model) | ||||
| { | { | ||||
| @@ -69,15 +90,15 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| private void Update(Model model) | private void Update(Model model) | ||||
| { | { | ||||
| AFKChannelId = model.AFKChannelId; | |||||
| _afkChannelId = model.AFKChannelId; | |||||
| AFKTimeout = model.AFKTimeout; | AFKTimeout = model.AFKTimeout; | ||||
| EmbedChannelId = model.EmbedChannelId; | |||||
| _embedChannelId = model.EmbedChannelId; | |||||
| IsEmbeddable = model.EmbedEnabled; | IsEmbeddable = model.EmbedEnabled; | ||||
| Features = model.Features; | Features = model.Features; | ||||
| _iconId = model.Icon; | _iconId = model.Icon; | ||||
| Name = model.Name; | Name = model.Name; | ||||
| OwnerId = model.OwnerId; | |||||
| VoiceRegionId = model.Region; | |||||
| _ownerId = model.OwnerId; | |||||
| VoiceRegion = Discord.GetVoiceRegion(model.Region); | |||||
| _splashId = model.Splash; | _splashId = model.Splash; | ||||
| VerificationLevel = model.VerificationLevel; | VerificationLevel = model.VerificationLevel; | ||||
| @@ -99,20 +120,6 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| _roles = roles; | _roles = roles; | ||||
| } | } | ||||
| private void Update(EmbedModel model) | |||||
| { | |||||
| IsEmbeddable = model.Enabled; | |||||
| EmbedChannelId = model.ChannelId; | |||||
| } | |||||
| private void Update(IEnumerable<RoleModel> models) | |||||
| { | |||||
| Role role; | |||||
| foreach (var model in models) | |||||
| { | |||||
| if (_roles.TryGetValue(model.Id, out role)) | |||||
| role.Update(model); | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Modify(Action<ModifyGuildParams> func) | public async Task Modify(Action<ModifyGuildParams> func) | ||||
| @@ -121,7 +128,7 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyGuildParams(); | var args = new ModifyGuildParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ModifyEmbed(Action<ModifyGuildEmbedParams> func) | public async Task ModifyEmbed(Action<ModifyGuildEmbedParams> func) | ||||
| @@ -130,75 +137,66 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyGuildEmbedParams(); | var args = new ModifyGuildEmbedParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args) | public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args) | ||||
| { | { | ||||
| await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ModifyRoles(IEnumerable<ModifyGuildRolesParams> args) | public async Task ModifyRoles(IEnumerable<ModifyGuildRolesParams> args) | ||||
| { | { | ||||
| await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Leave() | public async Task Leave() | ||||
| { | { | ||||
| await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IEnumerable<User>> GetBans() | public async Task<IEnumerable<User>> GetBans() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetGuildBans(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new PublicUser(Discord, x)); | |||||
| var models = await Discord.ApiClient.GetGuildBans(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new User(Discord, x)); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task AddBan(IUser user, int pruneDays = 0) => AddBan(user, pruneDays); | public Task AddBan(IUser user, int pruneDays = 0) => AddBan(user, pruneDays); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task AddBan(ulong userId, int pruneDays = 0) | public async Task AddBan(ulong userId, int pruneDays = 0) | ||||
| { | { | ||||
| var args = new CreateGuildBanParams() | |||||
| { | |||||
| PruneDays = pruneDays | |||||
| }; | |||||
| await Discord.APIClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); | |||||
| var args = new CreateGuildBanParams() { PruneDays = pruneDays }; | |||||
| await Discord.ApiClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task RemoveBan(IUser user) => RemoveBan(user.Id); | public Task RemoveBan(IUser user) => RemoveBan(user.Id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task RemoveBan(ulong userId) | public async Task RemoveBan(ulong userId) | ||||
| { | { | ||||
| await Discord.APIClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); | |||||
| await Discord.ApiClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <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> | ||||
| public async Task<GuildChannel> GetChannel(ulong id) | |||||
| public GuildChannel GetChannel(ulong id) | |||||
| { | { | ||||
| var model = await Discord.APIClient.GetChannel(Id, id).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return ToChannel(model); | |||||
| GuildChannel channel; | |||||
| if (_channels.TryGetValue(id, out channel)) | |||||
| return channel; | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> Gets a collection of all channels in this guild. </summary> | |||||
| public async Task<IEnumerable<GuildChannel>> GetChannels() | |||||
| { | |||||
| var models = await Discord.APIClient.GetGuildChannels(Id).ConfigureAwait(false); | |||||
| return models.Select(x => ToChannel(x)); | |||||
| } | |||||
| /// <summary> Creates a new text channel. </summary> | /// <summary> Creates a new text channel. </summary> | ||||
| public async Task<TextChannel> CreateTextChannel(string name) | public async Task<TextChannel> CreateTextChannel(string name) | ||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; | var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; | ||||
| var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| return new TextChannel(this, model); | return new TextChannel(this, model); | ||||
| } | } | ||||
| /// <summary> Creates a new voice channel. </summary> | /// <summary> Creates a new voice channel. </summary> | ||||
| @@ -207,28 +205,22 @@ namespace Discord.WebSocket | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; | var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; | ||||
| var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); | |||||
| return new VoiceChannel(this, model); | return new VoiceChannel(this, model); | ||||
| } | } | ||||
| /// <summary> Gets a collection of all integrations attached to this guild. </summary> | |||||
| public async Task<IEnumerable<GuildIntegration>> GetIntegrations() | |||||
| { | |||||
| var models = await Discord.APIClient.GetGuildIntegrations(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new GuildIntegration(this, x)); | |||||
| } | |||||
| /// <summary> Creates a new integration for this guild. </summary> | /// <summary> Creates a new integration for this guild. </summary> | ||||
| public async Task<GuildIntegration> CreateIntegration(ulong id, string type) | public async Task<GuildIntegration> CreateIntegration(ulong id, string type) | ||||
| { | { | ||||
| var args = new CreateGuildIntegrationParams { Id = id, Type = type }; | var args = new CreateGuildIntegrationParams { Id = id, Type = type }; | ||||
| var model = await Discord.APIClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); | |||||
| return new GuildIntegration(this, model); | return new GuildIntegration(this, model); | ||||
| } | } | ||||
| /// <summary> Gets a collection of all invites to this guild. </summary> | /// <summary> Gets a collection of all invites to this guild. </summary> | ||||
| public async Task<IEnumerable<InviteMetadata>> GetInvites() | public async Task<IEnumerable<InviteMetadata>> GetInvites() | ||||
| { | { | ||||
| var models = await Discord.APIClient.GetGuildInvites(Id).ConfigureAwait(false); | |||||
| var models = await Discord.ApiClient.GetGuildInvites(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new InviteMetadata(Discord, x)); | return models.Select(x => new InviteMetadata(Discord, x)); | ||||
| } | } | ||||
| /// <summary> Creates a new invite to this guild. </summary> | /// <summary> Creates a new invite to this guild. </summary> | ||||
| @@ -244,7 +236,7 @@ namespace Discord.WebSocket | |||||
| Temporary = isTemporary, | Temporary = isTemporary, | ||||
| XkcdPass = withXkcd | XkcdPass = withXkcd | ||||
| }; | }; | ||||
| var model = await Discord.APIClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateChannelInvite(Id, args).ConfigureAwait(false); | |||||
| return new InviteMetadata(Discord, model); | return new InviteMetadata(Discord, model); | ||||
| } | } | ||||
| @@ -262,7 +254,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| var model = await Discord.APIClient.CreateGuildRole(Id).ConfigureAwait(false); | |||||
| var model = await Discord.ApiClient.CreateGuildRole(Id).ConfigureAwait(false); | |||||
| var role = new Role(this, model); | var role = new Role(this, model); | ||||
| await role.Modify(x => | await role.Modify(x => | ||||
| @@ -275,43 +267,23 @@ namespace Discord.WebSocket | |||||
| return role; | return role; | ||||
| } | } | ||||
| /// <summary> Gets a collection of all users in this guild. </summary> | |||||
| public async Task<IEnumerable<GuildUser>> GetUsers() | |||||
| { | |||||
| var args = new GetGuildMembersParams(); | |||||
| var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new GuildUser(this, x)); | |||||
| } | |||||
| /// <summary> Gets a paged collection of all users in this guild. </summary> | |||||
| public async Task<IEnumerable<GuildUser>> GetUsers(int limit, int offset) | |||||
| { | |||||
| var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; | |||||
| var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); | |||||
| return models.Select(x => new GuildUser(this, x)); | |||||
| } | |||||
| /// <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> | ||||
| public async Task<GuildUser> GetUser(ulong id) | |||||
| public GuildUser GetUser(ulong id) | |||||
| { | { | ||||
| var model = await Discord.APIClient.GetGuildMember(Id, id).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return new GuildUser(this, model); | |||||
| GuildUser user; | |||||
| if (_members.TryGetValue(id, out user)) | |||||
| return user; | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <summary> Gets a the current user. </summary> | |||||
| public async Task<GuildUser> GetCurrentUser() | |||||
| { | |||||
| var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); | |||||
| return await GetUser(currentUser.Id).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<int> PruneUsers(int days = 30, bool simulate = false) | public async Task<int> PruneUsers(int days = 30, bool simulate = false) | ||||
| { | { | ||||
| var args = new GuildPruneParams() { Days = days }; | var args = new GuildPruneParams() { Days = days }; | ||||
| GetGuildPruneCountResponse model; | GetGuildPruneCountResponse model; | ||||
| if (simulate) | if (simulate) | ||||
| model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); | |||||
| model = await Discord.ApiClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); | |||||
| else | else | ||||
| model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false); | |||||
| model = await Discord.ApiClient.BeginGuildPrune(Id, args).ConfigureAwait(false); | |||||
| return model.Pruned; | return model.Pruned; | ||||
| } | } | ||||
| @@ -331,15 +303,22 @@ namespace Discord.WebSocket | |||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
| IEnumerable<Emoji> IGuild.Emojis => Emojis; | IEnumerable<Emoji> IGuild.Emojis => Emojis; | ||||
| ulong IGuild.EveryoneRoleId => EveryoneRole.Id; | |||||
| IEnumerable<string> IGuild.Features => Features; | IEnumerable<string> IGuild.Features => Features; | ||||
| ulong? IGuild.AFKChannelId => _afkChannelId; | |||||
| ulong IGuild.DefaultChannelId => Id; | |||||
| ulong? IGuild.EmbedChannelId => _embedChannelId; | |||||
| ulong IGuild.EveryoneRoleId => EveryoneRole.Id; | |||||
| ulong IGuild.OwnerId => _ownerId; | |||||
| string IGuild.VoiceRegionId => VoiceRegion.Id; | |||||
| bool IUserGuild.IsOwner => CurrentUser.Id == _ownerId; | |||||
| GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions; | |||||
| async Task<IEnumerable<IUser>> IGuild.GetBans() | async Task<IEnumerable<IUser>> IGuild.GetBans() | ||||
| => await GetBans().ConfigureAwait(false); | => await GetBans().ConfigureAwait(false); | ||||
| async Task<IGuildChannel> IGuild.GetChannel(ulong id) | |||||
| => await GetChannel(id).ConfigureAwait(false); | |||||
| async Task<IEnumerable<IGuildChannel>> IGuild.GetChannels() | |||||
| => await GetChannels().ConfigureAwait(false); | |||||
| Task<IGuildChannel> IGuild.GetChannel(ulong id) | |||||
| => Task.FromResult<IGuildChannel>(GetChannel(id)); | |||||
| Task<IEnumerable<IGuildChannel>> IGuild.GetChannels() | |||||
| => Task.FromResult<IEnumerable<IGuildChannel>>(Channels); | |||||
| async Task<IInviteMetadata> IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) | async Task<IInviteMetadata> IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) | ||||
| => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); | => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); | ||||
| async Task<IRole> IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted) | async Task<IRole> IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted) | ||||
| @@ -354,12 +333,12 @@ namespace Discord.WebSocket | |||||
| => Task.FromResult<IRole>(GetRole(id)); | => Task.FromResult<IRole>(GetRole(id)); | ||||
| Task<IEnumerable<IRole>> IGuild.GetRoles() | Task<IEnumerable<IRole>> IGuild.GetRoles() | ||||
| => Task.FromResult<IEnumerable<IRole>>(Roles); | => Task.FromResult<IEnumerable<IRole>>(Roles); | ||||
| async Task<IGuildUser> IGuild.GetUser(ulong id) | |||||
| => await GetUser(id).ConfigureAwait(false); | |||||
| async Task<IGuildUser> IGuild.GetCurrentUser() | |||||
| => await GetCurrentUser().ConfigureAwait(false); | |||||
| async Task<IEnumerable<IGuildUser>> IGuild.GetUsers() | |||||
| => await GetUsers().ConfigureAwait(false); | |||||
| Task<IGuildUser> IGuild.GetUser(ulong id) | |||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| Task<IGuildUser> IGuild.GetCurrentUser() | |||||
| => Task.FromResult<IGuildUser>(CurrentUser); | |||||
| Task<IEnumerable<IGuildUser>> IGuild.GetUsers() | |||||
| => Task.FromResult<IEnumerable<IGuildUser>>(Users); | |||||
| Task IUpdateable.Update() | Task IUpdateable.Update() | ||||
| => Task.CompletedTask; | => Task.CompletedTask; | ||||
| } | } | ||||
| @@ -31,7 +31,7 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Role Role { get; private set; } | public Role Role { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public User User { get; private set; } | |||||
| public GuildUser User { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IntegrationAccount Account { get; private set; } | public IntegrationAccount Account { get; private set; } | ||||
| internal DiscordClient Discord => Guild.Discord; | internal DiscordClient Discord => Guild.Discord; | ||||
| @@ -54,13 +54,13 @@ namespace Discord.WebSocket | |||||
| SyncedAt = model.SyncedAt; | SyncedAt = model.SyncedAt; | ||||
| Role = Guild.GetRole(model.RoleId); | Role = Guild.GetRole(model.RoleId); | ||||
| User = new PublicUser(Discord, model.User); | |||||
| User = Guild.GetUser(model.User.Id); | |||||
| } | } | ||||
| /// <summary> </summary> | /// <summary> </summary> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| { | { | ||||
| await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> </summary> | /// <summary> </summary> | ||||
| public async Task Modify(Action<ModifyGuildIntegrationParams> func) | public async Task Modify(Action<ModifyGuildIntegrationParams> func) | ||||
| @@ -69,12 +69,12 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyGuildIntegrationParams(); | var args = new ModifyGuildIntegrationParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> </summary> | /// <summary> </summary> | ||||
| public async Task Sync() | public async Task Sync() | ||||
| { | { | ||||
| await Discord.APIClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| @@ -3,11 +3,13 @@ using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Message; | using Model = Discord.API.Message; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| //TODO: Support mention_roles | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class Message : IMessage | public class Message : IMessage | ||||
| { | { | ||||
| @@ -28,33 +30,36 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IMessageChannel Channel { get; } | public IMessageChannel Channel { get; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public User Author { get; } | |||||
| public IUser Author { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<Attachment> Attachments { get; private set; } | public IReadOnlyList<Attachment> Attachments { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<Embed> Embeds { get; private set; } | public IReadOnlyList<Embed> Embeds { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<PublicUser> MentionedUsers { get; private set; } | |||||
| public IReadOnlyList<GuildUser> MentionedUsers { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<ulong> MentionedChannelIds { get; private set; } | |||||
| public IReadOnlyList<GuildChannel> MentionedChannels { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<ulong> MentionedRoleIds { get; private set; } | |||||
| public IReadOnlyList<Role> MentionedRoles { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord; | internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord; | ||||
| internal Message(IMessageChannel channel, Model model) | |||||
| internal Message(IMessageChannel channel, IUser author, Model model) | |||||
| { | { | ||||
| Id = model.Id; | Id = model.Id; | ||||
| Channel = channel; | Channel = channel; | ||||
| Author = new PublicUser(Discord, model.Author); | |||||
| Author = author; | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| private void Update(Model model) | private void Update(Model model) | ||||
| { | { | ||||
| var guildChannel = Channel as GuildChannel; | |||||
| var guild = guildChannel?.Guild; | |||||
| IsTTS = model.IsTextToSpeech; | IsTTS = model.IsTextToSpeech; | ||||
| Timestamp = model.Timestamp; | Timestamp = model.Timestamp; | ||||
| EditedTimestamp = model.EditedTimestamp; | EditedTimestamp = model.EditedTimestamp; | ||||
| @@ -80,38 +85,38 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| Embeds = Array.Empty<Embed>(); | Embeds = Array.Empty<Embed>(); | ||||
| if (model.Mentions.Length > 0) | |||||
| if (guildChannel != null && model.Mentions.Length > 0) | |||||
| { | { | ||||
| var discord = Discord; | |||||
| var builder = ImmutableArray.CreateBuilder<PublicUser>(model.Mentions.Length); | |||||
| var builder = ImmutableArray.CreateBuilder<GuildUser>(model.Mentions.Length); | |||||
| for (int i = 0; i < model.Mentions.Length; i++) | for (int i = 0; i < model.Mentions.Length; i++) | ||||
| builder.Add(new PublicUser(discord, model.Mentions[i])); | |||||
| { | |||||
| var user = guild.GetUser(model.Mentions[i].Id); | |||||
| if (user != null) | |||||
| builder.Add(user); | |||||
| } | |||||
| MentionedUsers = builder.ToArray(); | MentionedUsers = builder.ToArray(); | ||||
| } | } | ||||
| else | else | ||||
| MentionedUsers = Array.Empty<PublicUser>(); | |||||
| MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); | |||||
| MentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); | |||||
| if (model.IsMentioningEveryone) | |||||
| MentionedUsers = Array.Empty<GuildUser>(); | |||||
| if (guildChannel != null/* && model.Content != null*/) | |||||
| { | { | ||||
| ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; | |||||
| if (guildId != null) | |||||
| { | |||||
| if (MentionedRoleIds.Count == 0) | |||||
| MentionedRoleIds = ImmutableArray.Create(guildId.Value); | |||||
| else | |||||
| { | |||||
| var builder = ImmutableArray.CreateBuilder<ulong>(MentionedRoleIds.Count + 1); | |||||
| builder.AddRange(MentionedRoleIds); | |||||
| builder.Add(guildId.Value); | |||||
| MentionedRoleIds = builder.ToImmutable(); | |||||
| } | |||||
| } | |||||
| MentionedChannels = MentionUtils.GetChannelMentions(model.Content).Select(x => guild.GetChannel(x)).Where(x => x != null).ToImmutableArray(); | |||||
| var mentionedRoles = MentionUtils.GetRoleMentions(model.Content).Select(x => guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); | |||||
| if (model.IsMentioningEveryone) | |||||
| mentionedRoles = mentionedRoles.Add(guild.EveryoneRole); | |||||
| MentionedRoles = mentionedRoles; | |||||
| } | |||||
| else | |||||
| { | |||||
| MentionedChannels = Array.Empty<GuildChannel>(); | |||||
| MentionedRoles = Array.Empty<Role>(); | |||||
| } | } | ||||
| Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); | Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); | ||||
| Author.Update(model.Author); | |||||
| //Author.Update(model.Author); //TODO: Uncomment this somehow | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -124,9 +129,9 @@ namespace Discord.WebSocket | |||||
| var guildChannel = Channel as GuildChannel; | var guildChannel = Channel as GuildChannel; | ||||
| if (guildChannel != null) | if (guildChannel != null) | ||||
| await Discord.APIClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); | |||||
| else | else | ||||
| await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -134,18 +139,17 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| var guildChannel = Channel as GuildChannel; | var guildChannel = Channel as GuildChannel; | ||||
| if (guildChannel != null) | if (guildChannel != null) | ||||
| await Discord.APIClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); | |||||
| else | else | ||||
| await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); | |||||
| await Discord.ApiClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); | |||||
| } | } | ||||
| public override string ToString() => Text; | public override string ToString() => Text; | ||||
| private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | ||||
| IUser IMessage.Author => Author; | IUser IMessage.Author => Author; | ||||
| IReadOnlyList<Attachment> IMessage.Attachments => Attachments; | |||||
| IReadOnlyList<Embed> IMessage.Embeds => Embeds; | |||||
| IReadOnlyList<ulong> IMessage.MentionedChannelIds => MentionedChannelIds; | |||||
| IReadOnlyList<IUser> IMessage.MentionedUsers => MentionedUsers; | IReadOnlyList<IUser> IMessage.MentionedUsers => MentionedUsers; | ||||
| IReadOnlyList<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); | |||||
| IReadOnlyList<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -35,6 +35,7 @@ namespace Discord.WebSocket | |||||
| public bool IsEveryone => Id == Guild.Id; | public bool IsEveryone => Id == Guild.Id; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Mention => MentionUtils.Mention(this); | public string Mention => MentionUtils.Mention(this); | ||||
| public IEnumerable<GuildUser> Users => Guild.Users.Where(x => x.Roles.Any(y => y.Id == Id)); | |||||
| internal DiscordClient Discord => Guild.Discord; | internal DiscordClient Discord => Guild.Discord; | ||||
| internal Role(Guild guild, Model model) | internal Role(Guild guild, Model model) | ||||
| @@ -60,23 +61,19 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyGuildRoleParams(); | var args = new ModifyGuildRoleParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> Deletes this message. </summary> | /// <summary> Deletes this message. </summary> | ||||
| public async Task Delete() | public async Task Delete() | ||||
| => await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); | |||||
| => await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
| ulong IRole.GuildId => Guild.Id; | ulong IRole.GuildId => Guild.Id; | ||||
| async Task<IEnumerable<IGuildUser>> IRole.GetUsers() | |||||
| { | |||||
| //A tad hacky, but it works | |||||
| var models = await Discord.APIClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); | |||||
| return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); | |||||
| } | |||||
| Task<IEnumerable<IGuildUser>> IRole.GetUsers() | |||||
| => Task.FromResult<IEnumerable<IGuildUser>>(Users); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,20 +0,0 @@ | |||||
| using Model = Discord.API.User; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class DMUser : User, IDMUser | |||||
| { | |||||
| /// <inheritdoc /> | |||||
| public DMChannel Channel { get; } | |||||
| internal override DiscordClient Discord => Channel.Discord; | |||||
| internal DMUser(DMChannel channel, Model model) | |||||
| : base(model) | |||||
| { | |||||
| Channel = channel; | |||||
| } | |||||
| IDMChannel IDMUser.Channel => Channel; | |||||
| } | |||||
| } | |||||
| @@ -8,11 +8,12 @@ using Model = Discord.API.GuildMember; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| public class GuildUser : User, IGuildUser | |||||
| public class GuildUser : IGuildUser | |||||
| { | { | ||||
| private ImmutableArray<Role> _roles; | private ImmutableArray<Role> _roles; | ||||
| public Guild Guild { get; } | public Guild Guild { get; } | ||||
| public User GlobalUser { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public bool IsDeaf { get; private set; } | public bool IsDeaf { get; private set; } | ||||
| @@ -23,18 +24,38 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Nickname { get; private set; } | public string Nickname { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public UserStatus Status { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public Game? CurrentGame { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public VoiceChannel VoiceChannel { get; private set; } | public VoiceChannel VoiceChannel { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public GuildPermissions GuildPermissions { get; private set; } | public GuildPermissions GuildPermissions { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IReadOnlyList<Role> Roles => _roles; | public IReadOnlyList<Role> Roles => _roles; | ||||
| internal override DiscordClient Discord => Guild.Discord; | |||||
| /// <inheritdoc /> | |||||
| public string AvatarUrl => GlobalUser.AvatarUrl; | |||||
| /// <inheritdoc /> | |||||
| public ushort Discriminator => GlobalUser.Discriminator; | |||||
| /// <inheritdoc /> | |||||
| public bool IsBot => GlobalUser.IsBot; | |||||
| /// <inheritdoc /> | |||||
| public string Username => GlobalUser.Username; | |||||
| /// <inheritdoc /> | |||||
| public DateTime CreatedAt => GlobalUser.CreatedAt; | |||||
| /// <inheritdoc /> | |||||
| public ulong Id => GlobalUser.Id; | |||||
| /// <inheritdoc /> | |||||
| public string Mention => GlobalUser.Mention; | |||||
| internal DiscordClient Discord => Guild.Discord; | |||||
| internal GuildUser(Guild guild, Model model) | |||||
| : base(model.User) | |||||
| internal GuildUser(User globalUser, Guild guild, Model model) | |||||
| { | { | ||||
| GlobalUser = globalUser; | |||||
| Guild = guild; | Guild = guild; | ||||
| globalUser.Update(model.User); | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| internal void Update(Model model) | internal void Update(Model model) | ||||
| @@ -57,31 +78,6 @@ namespace Discord.WebSocket | |||||
| GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); | GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); | ||||
| } | } | ||||
| public bool HasRole(IRole role) | |||||
| { | |||||
| for (int i = 0; i < _roles.Length; i++) | |||||
| { | |||||
| if (_roles[i].Id == role.Id) | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public async Task Kick() | |||||
| { | |||||
| await Discord.APIClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | |||||
| public GuildPermissions GetGuildPermissions() | |||||
| { | |||||
| return new GuildPermissions(Permissions.ResolveGuild(this)); | |||||
| } | |||||
| public ChannelPermissions GetPermissions(IGuildChannel channel) | |||||
| { | |||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
| return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); | |||||
| } | |||||
| public async Task Modify(Action<ModifyGuildMemberParams> func) | public async Task Modify(Action<ModifyGuildMemberParams> func) | ||||
| { | { | ||||
| if (func == null) throw new NullReferenceException(nameof(func)); | if (func == null) throw new NullReferenceException(nameof(func)); | ||||
| @@ -89,17 +85,17 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyGuildMemberParams(); | var args = new ModifyGuildMemberParams(); | ||||
| func(args); | func(args); | ||||
| bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; | |||||
| bool isCurrentUser = Discord.CurrentUser.Id == Id; | |||||
| if (isCurrentUser && args.Nickname.IsSpecified) | if (isCurrentUser && args.Nickname.IsSpecified) | ||||
| { | { | ||||
| var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; | var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; | ||||
| await Discord.APIClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); | |||||
| args.Nickname = new API.Optional<string>(); //Remove | args.Nickname = new API.Optional<string>(); //Remove | ||||
| } | } | ||||
| if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) | if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) | ||||
| { | { | ||||
| await Discord.APIClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); | |||||
| if (args.Deaf.IsSpecified) | if (args.Deaf.IsSpecified) | ||||
| IsDeaf = args.Deaf.Value; | IsDeaf = args.Deaf.Value; | ||||
| if (args.Mute.IsSpecified) | if (args.Mute.IsSpecified) | ||||
| @@ -111,6 +107,26 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| public async Task Kick() | |||||
| { | |||||
| await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); | |||||
| } | |||||
| public GuildPermissions GetGuildPermissions() | |||||
| { | |||||
| return new GuildPermissions(Permissions.ResolveGuild(this)); | |||||
| } | |||||
| public ChannelPermissions GetPermissions(IGuildChannel channel) | |||||
| { | |||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
| return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); | |||||
| } | |||||
| public async Task<DMChannel> CreateDMChannel() | |||||
| { | |||||
| return await GlobalUser.CreateDMChannel().ConfigureAwait(false); | |||||
| } | |||||
| IGuild IGuildUser.Guild => Guild; | IGuild IGuildUser.Guild => Guild; | ||||
| IReadOnlyList<IRole> IGuildUser.Roles => Roles; | IReadOnlyList<IRole> IGuildUser.Roles => Roles; | ||||
| @@ -118,7 +134,10 @@ namespace Discord.WebSocket | |||||
| ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) | ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) | ||||
| => GetPermissions(channel); | => GetPermissions(channel); | ||||
| async Task<IDMChannel> IUser.CreateDMChannel() | |||||
| => await CreateDMChannel().ConfigureAwait(false); | |||||
| Task IUpdateable.Update() | Task IUpdateable.Update() | ||||
| => Task.CompletedTask; | => Task.CompletedTask; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,15 +0,0 @@ | |||||
| using Model = Discord.API.User; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class PublicUser : User | |||||
| { | |||||
| internal override DiscordClient Discord { get; } | |||||
| internal PublicUser(DiscordClient discord, Model model) | |||||
| : base(model) | |||||
| { | |||||
| Discord = discord; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -7,17 +7,14 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| public class SelfUser : User, ISelfUser | public class SelfUser : User, ISelfUser | ||||
| { | { | ||||
| internal override DiscordClient Discord { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Email { get; private set; } | public string Email { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public bool IsVerified { get; private set; } | public bool IsVerified { get; private set; } | ||||
| internal SelfUser(DiscordClient discord, Model model) | internal SelfUser(DiscordClient discord, Model model) | ||||
| : base(model) | |||||
| : base(discord, model) | |||||
| { | { | ||||
| Discord = discord; | |||||
| } | } | ||||
| internal override void Update(Model model) | internal override void Update(Model model) | ||||
| { | { | ||||
| @@ -34,7 +31,7 @@ namespace Discord.WebSocket | |||||
| var args = new ModifyCurrentUserParams(); | var args = new ModifyCurrentUserParams(); | ||||
| func(args); | func(args); | ||||
| await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false); | |||||
| await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false); | |||||
| } | } | ||||
| Task IUpdateable.Update() | Task IUpdateable.Update() | ||||
| @@ -6,14 +6,15 @@ using Model = Discord.API.User; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| //TODO: Unload when there are no more references via DMUser or GuildUser | |||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| public abstract class User : IUser | |||||
| public class User : IUser | |||||
| { | { | ||||
| private string _avatarId; | private string _avatarId; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| internal abstract DiscordClient Discord { get; } | |||||
| internal DiscordClient Discord { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ushort Discriminator { get; private set; } | public ushort Discriminator { get; private set; } | ||||
| @@ -21,6 +22,8 @@ namespace Discord.WebSocket | |||||
| public bool IsBot { get; private set; } | public bool IsBot { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Username { get; private set; } | public string Username { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public DMChannel DMChannel { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); | public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); | ||||
| @@ -31,8 +34,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string NicknameMention => MentionUtils.Mention(this, true); | public string NicknameMention => MentionUtils.Mention(this, true); | ||||
| internal User(Model model) | |||||
| internal User(DiscordClient discord, Model model) | |||||
| { | { | ||||
| Discord = discord; | |||||
| Id = model.Id; | Id = model.Id; | ||||
| Update(model); | Update(model); | ||||
| @@ -47,10 +51,16 @@ namespace Discord.WebSocket | |||||
| public async Task<DMChannel> CreateDMChannel() | public async Task<DMChannel> CreateDMChannel() | ||||
| { | { | ||||
| var args = new CreateDMChannelParams { RecipientId = Id }; | |||||
| var model = await Discord.APIClient.CreateDMChannel(args).ConfigureAwait(false); | |||||
| var channel = DMChannel; | |||||
| if (channel == null) | |||||
| { | |||||
| var args = new CreateDMChannelParams { RecipientId = Id }; | |||||
| var model = await Discord.ApiClient.CreateDMChannel(args).ConfigureAwait(false); | |||||
| return new DMChannel(Discord, model); | |||||
| channel = new DMChannel(Discord, this, model); | |||||
| DMChannel = channel; | |||||
| } | |||||
| return channel; | |||||
| } | } | ||||
| public override string ToString() => $"{Username}#{Discriminator}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class ChannelEventArgs : EventArgs | |||||
| { | |||||
| public IChannel Channel { get; } | |||||
| public ChannelEventArgs(IChannel channel) | |||||
| { | |||||
| Channel = channel; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class ChannelUpdatedEventArgs : ChannelEventArgs | |||||
| { | |||||
| public IChannel Before { get; } | |||||
| public IChannel After => Channel; | |||||
| public ChannelUpdatedEventArgs(IChannel before, IChannel after) | |||||
| : base(after) | |||||
| { | |||||
| Before = before; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class CurrentUserEventArgs : EventArgs | |||||
| { | |||||
| public SelfUser CurrentUser { get; } | |||||
| public CurrentUserEventArgs(SelfUser currentUser) | |||||
| { | |||||
| CurrentUser = currentUser; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class CurrentUserUpdatedEventArgs : CurrentUserEventArgs | |||||
| { | |||||
| public SelfUser Before { get; } | |||||
| public SelfUser After => CurrentUser; | |||||
| public CurrentUserUpdatedEventArgs(SelfUser before, SelfUser after) | |||||
| : base(after) | |||||
| { | |||||
| Before = before; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class DisconnectedEventArgs : EventArgs | |||||
| { | |||||
| public bool WasUnexpected { get; } | |||||
| public Exception Exception { get; } | |||||
| public DisconnectedEventArgs(bool wasUnexpected, Exception exception = null) | |||||
| { | |||||
| WasUnexpected = wasUnexpected; | |||||
| Exception = exception; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class GuildEventArgs : EventArgs | |||||
| { | |||||
| public Guild Guild { get; } | |||||
| public GuildEventArgs(Guild guild) | |||||
| { | |||||
| Guild = guild; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class GuildUpdatedEventArgs : GuildEventArgs | |||||
| { | |||||
| public Guild Before { get; } | |||||
| public Guild After => Guild; | |||||
| public GuildUpdatedEventArgs(Guild before, Guild after) | |||||
| : base(after) | |||||
| { | |||||
| Before = before; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class MessageEventArgs : EventArgs | |||||
| { | |||||
| public Message Message { get; } | |||||
| public MessageEventArgs(Message message) | |||||
| { | |||||
| Message = message; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class MessageUpdatedEventArgs : MessageEventArgs | |||||
| { | |||||
| public Message Before { get; } | |||||
| public Message After => Message; | |||||
| public MessageUpdatedEventArgs(Message before, Message after) | |||||
| : base(after) | |||||
| { | |||||
| Before = before; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class RoleEventArgs : EventArgs | |||||
| { | |||||
| public Role Role { get; } | |||||
| public RoleEventArgs(Role role) | |||||
| { | |||||
| Role = role; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class RoleUpdatedEventArgs : RoleEventArgs | |||||
| { | |||||
| public Role Before { get; } | |||||
| public Role After => Role; | |||||
| public RoleUpdatedEventArgs(Role before, Role after) | |||||
| : base(after) | |||||
| { | |||||
| Before = before; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class TypingEventArgs : EventArgs | |||||
| { | |||||
| public IMessageChannel Channel { get; } | |||||
| public IUser User { get; } | |||||
| public TypingEventArgs(IMessageChannel channel, IUser user) | |||||
| { | |||||
| Channel = channel; | |||||
| User = user; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class UserEventArgs : EventArgs | |||||
| { | |||||
| public IUser User { get; } | |||||
| public UserEventArgs(IUser user) | |||||
| { | |||||
| User = user; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class UserUpdatedEventArgs : UserEventArgs | |||||
| { | |||||
| public IUser Before { get; } | |||||
| public IUser After => User; | |||||
| public UserUpdatedEventArgs(IUser before, IUser after) | |||||
| : base(after) | |||||
| { | |||||
| Before = before; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class VoiceChannelEventArgs : EventArgs | |||||
| { | |||||
| public VoiceChannel Channel { get; } | |||||
| public VoiceChannelEventArgs(VoiceChannel channel) | |||||
| { | |||||
| Channel = channel; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -99,8 +99,9 @@ namespace Discord.WebSocket | |||||
| RelativeDirection = dir, | RelativeDirection = dir, | ||||
| RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id | RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id | ||||
| }; | }; | ||||
| var downloadedMessages = await _discord.APIClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); | |||||
| return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, x))).ToImmutableArray(); | |||||
| var downloadedMessages = await _discord.ApiClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); | |||||
| //TODO: Ugly channel cast | |||||
| return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, (_channel as Channel).GetUser(x.Id), x))).ToImmutableArray(); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,14 +1,38 @@ | |||||
| { | |||||
| { | |||||
| "version": "1.0.0-dev", | |||||
| "description": "A Discord.Net extension adding voice support.", | |||||
| "authors": [ "RogueException" ], | |||||
| "buildOptions": { | |||||
| "allowUnsafe": true, | |||||
| "warningsAsErrors": false | |||||
| }, | |||||
| "packOptions": { | |||||
| "tags": [ "discord", "discordapp" ], | |||||
| "licenseUrl": "http://opensource.org/licenses/MIT", | |||||
| "projectUrl": "https://github.com/RogueException/Discord.Net", | |||||
| "repository": { | |||||
| "type": "git", | |||||
| "url": "git://github.com/RogueException/Discord.Net" | |||||
| } | |||||
| }, | |||||
| "dependencies": { | "dependencies": { | ||||
| "NETStandard.Library": "1.5.0-rc2-24027", | |||||
| "Newtonsoft.Json": "8.0.3", | "Newtonsoft.Json": "8.0.3", | ||||
| "System.Collections.Immutable": "1.1.37" | |||||
| "System.Collections.Immutable": "1.2.0-rc2-24027", | |||||
| "System.Net.Websockets.Client": "4.0.0-rc2-24027", | |||||
| "System.Runtime.Serialization.Primitives": "4.1.1-rc2-24027" | |||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| "net461": { } | |||||
| }, | |||||
| "runtimes": { | |||||
| "win": { } | |||||
| "netstandard1.3": { | |||||
| "imports": [ | |||||
| "dotnet5.4", | |||||
| "dnxcore50", | |||||
| "portable-net45+win8" | |||||
| ] | |||||
| } | |||||
| } | } | ||||
| } | |||||
| } | |||||