Browse Source

Major cleanup, upgraded to RC2, ported several websocket features from 0.9.

pull/97/head
RogueException 9 years ago
parent
commit
b9d9191a30
75 changed files with 1364 additions and 915 deletions
  1. +7
    -7
      README.md
  2. +6
    -0
      global.json
  3. +2
    -2
      src/Discord.Net/API/DiscordAPIClient.cs
  4. +1
    -1
      src/Discord.Net/Common/Entities/Channels/IDMChannel.cs
  5. +1
    -1
      src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs
  6. +2
    -2
      src/Discord.Net/Common/Entities/Invites/Invite.cs
  7. +1
    -1
      src/Discord.Net/Common/Entities/Messages/IMessage.cs
  8. +29
    -15
      src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs
  9. +4
    -0
      src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs
  10. +10
    -3
      src/Discord.Net/Common/Entities/Permissions/Permissions.cs
  11. +1
    -1
      src/Discord.Net/Common/Entities/Users/Connection.cs
  12. +0
    -8
      src/Discord.Net/Common/Entities/Users/IDMUser.cs
  13. +0
    -3
      src/Discord.Net/Common/Entities/Users/IGuildUser.cs
  14. +1
    -0
      src/Discord.Net/Common/Entities/Users/IUser.cs
  15. +3
    -3
      src/Discord.Net/Common/MentionUtils.cs
  16. +0
    -243
      src/Discord.Net/Discord.Net.csproj
  17. +19
    -0
      src/Discord.Net/Discord.Net.xproj
  18. +1
    -2
      src/Discord.Net/IDiscordClient.cs
  19. +50
    -38
      src/Discord.Net/Net/Converters/DiscordContractResolver.cs
  20. +1
    -1
      src/Discord.Net/Net/Converters/OptionalConverter.cs
  21. +18
    -13
      src/Discord.Net/Net/Rest/DefaultRestClient.cs
  22. +1
    -3
      src/Discord.Net/Net/Rest/RestClientProvider.cs
  23. +11
    -0
      src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs
  24. +180
    -0
      src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs
  25. +19
    -0
      src/Discord.Net/Net/WebSockets/IWebSocketClient.cs
  26. +11
    -0
      src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs
  27. +4
    -0
      src/Discord.Net/Net/WebSockets/WebSocketProvider.cs
  28. +13
    -10
      src/Discord.Net/Properties/AssemblyInfo.cs
  29. +35
    -39
      src/Discord.Net/Rest/DiscordClient.cs
  30. +15
    -15
      src/Discord.Net/Rest/Entities/Channels/DMChannel.cs
  31. +9
    -9
      src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs
  32. +8
    -8
      src/Discord.Net/Rest/Entities/Channels/TextChannel.cs
  33. +1
    -1
      src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs
  34. +25
    -28
      src/Discord.Net/Rest/Entities/Guilds/Guild.cs
  35. +3
    -3
      src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs
  36. +2
    -2
      src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs
  37. +27
    -32
      src/Discord.Net/Rest/Entities/Message.cs
  38. +4
    -4
      src/Discord.Net/Rest/Entities/Role.cs
  39. +0
    -20
      src/Discord.Net/Rest/Entities/Users/DMUser.cs
  40. +4
    -14
      src/Discord.Net/Rest/Entities/Users/GuildUser.cs
  41. +2
    -2
      src/Discord.Net/Rest/Entities/Users/SelfUser.cs
  42. +3
    -3
      src/Discord.Net/Rest/Entities/Users/User.cs
  43. +238
    -72
      src/Discord.Net/WebSocket/DiscordClient.cs
  44. +39
    -0
      src/Discord.Net/WebSocket/DiscordSocketConfig.cs
  45. +37
    -0
      src/Discord.Net/WebSocket/Entities/Channels/Channel.cs
  46. +27
    -31
      src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs
  47. +22
    -18
      src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs
  48. +9
    -9
      src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs
  49. +1
    -1
      src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs
  50. +89
    -110
      src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs
  51. +5
    -5
      src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs
  52. +39
    -35
      src/Discord.Net/WebSocket/Entities/Message.cs
  53. +6
    -9
      src/Discord.Net/WebSocket/Entities/Role.cs
  54. +0
    -20
      src/Discord.Net/WebSocket/Entities/Users/DMUser.cs
  55. +51
    -32
      src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs
  56. +0
    -15
      src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs
  57. +2
    -5
      src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs
  58. +16
    -6
      src/Discord.Net/WebSocket/Entities/Users/User.cs
  59. +14
    -0
      src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs
  60. +14
    -0
      src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs
  61. +14
    -0
      src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs
  62. +14
    -0
      src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs
  63. +16
    -0
      src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs
  64. +14
    -0
      src/Discord.Net/WebSocket/Events/GuildEventArgs.cs
  65. +14
    -0
      src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs
  66. +14
    -0
      src/Discord.Net/WebSocket/Events/MessageEventArgs.cs
  67. +14
    -0
      src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs
  68. +14
    -0
      src/Discord.Net/WebSocket/Events/RoleEventArgs.cs
  69. +14
    -0
      src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs
  70. +16
    -0
      src/Discord.Net/WebSocket/Events/TypingEventArgs.cs
  71. +14
    -0
      src/Discord.Net/WebSocket/Events/UserEventArgs.cs
  72. +14
    -0
      src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs
  73. +14
    -0
      src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs
  74. +3
    -2
      src/Discord.Net/WebSocket/MessageCache.cs
  75. +32
    -8
      src/Discord.Net/project.json

+ 7
- 7
README.md View File

@@ -1,9 +1,11 @@
# Discord.Net v1.0.0-dev
[![Build status](https://ci.appveyor.com/api/projects/status/p0n69xhqgmoobycf/branch/master?svg=true)](https://ci.appveyor.com/project/foxbot/discord-net/branch/master)

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.

### Installation
@@ -16,9 +18,7 @@ You can download Discord.Net and its extensions from NuGet:
### Compiling
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 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

+ 6
- 0
global.json View File

@@ -0,0 +1,6 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview1-002702"
}
}

+ 2
- 2
src/Discord.Net/API/DiscordAPIClient.cs View File

@@ -17,7 +17,7 @@ using System.Threading.Tasks;

namespace Discord.API
{
public class DiscordAPIClient
public class DiscordApiClient
{
internal event EventHandler<SentRequestEventArgs> SentRequest;
@@ -30,7 +30,7 @@ namespace Discord.API
public IRestClient RestClient { get; private set; }
public IRequestQueue RequestQueue { get; private set; }

public DiscordAPIClient(RestClientProvider restClientProvider)
public DiscordApiClient(RestClientProvider restClientProvider)
{
_restClient = restClientProvider(DiscordConfig.ClientAPIUrl);
_restClient.SetHeader("accept", "*/*");


+ 1
- 1
src/Discord.Net/Common/Entities/Channels/IDMChannel.cs View File

@@ -5,7 +5,7 @@ namespace Discord
public interface IDMChannel : IMessageChannel, IUpdateable
{
/// <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>
Task Close();


+ 1
- 1
src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs View File

@@ -1,7 +1,7 @@
using System.Diagnostics;
using Model = Discord.API.VoiceRegion;

namespace Discord.Rest
namespace Discord
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class VoiceRegion : IVoiceRegion


+ 2
- 2
src/Discord.Net/Common/Entities/Invites/Invite.cs View File

@@ -47,13 +47,13 @@ namespace Discord
/// <inheritdoc />
public async Task Accept()
{
await Discord.APIClient.AcceptInvite(Code).ConfigureAwait(false);
await Discord.ApiClient.AcceptInvite(Code).ConfigureAwait(false);
}

/// <inheritdoc />
public async Task Delete()
{
await Discord.APIClient.DeleteInvite(Code).ConfigureAwait(false);
await Discord.ApiClient.DeleteInvite(Code).ConfigureAwait(false);
}

/// <inheritdoc />


+ 1
- 1
src/Discord.Net/Common/Entities/Messages/IMessage.cs View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.API.Rest;
using System.Collections.Generic;

namespace Discord
{


+ 29
- 15
src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs View File

@@ -7,23 +7,37 @@ namespace Discord
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct ChannelPermissions
{
#if CSHARP7
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 _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>
public static ChannelPermissions None { get; } = new ChannelPermissions();
/// <summary> Gets a ChannelPermissions that grants all permissions for a given channelType. </summary>
public static ChannelPermissions All(IChannel channel)
{
#if CSHARP7
switch (channel)
{
case ITextChannel _: return _allText;
case IVoiceChannel _: return _allVoice;
case IGuildChannel _: return _allDM;
case IDMChannel _: return _allDM;
default:
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>
@@ -70,9 +84,9 @@ namespace Discord
/// <summary> Creates a new ChannelPermissions with the provided packed value. </summary>
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? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null)
{
@@ -100,25 +114,25 @@ namespace Discord
}

/// <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 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) { }

/// <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? 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);
public List<ChannelPermission> ToList()
{
var perms = new List<ChannelPermission>();


+ 4
- 0
src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs View File

@@ -10,7 +10,11 @@ namespace Discord
/// <summary> Gets a blank GuildPermissions that grants no permissions. </summary>
public static readonly GuildPermissions None = new GuildPermissions();
/// <summary> Gets a GuildPermissions that grants all permissions. </summary>
#if CSHARP7
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>
public ulong RawValue { get; }


+ 10
- 3
src/Discord.Net/Common/Entities/Permissions/Permissions.cs View File

@@ -130,6 +130,7 @@ namespace Discord
if (overwrites.TryGetValue(user.Id, out entry))
resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue;

#if CSHARP7
switch (channel)
{
case ITextChannel _:
@@ -140,10 +141,16 @@ namespace Discord
if (!GetValue(resolvedPermissions, ChannelPermission.Connect))
resolvedPermissions = 0; //No read permission on a text channel removes all other permissions
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;


+ 1
- 1
src/Discord.Net/Common/Entities/Users/Connection.cs View File

@@ -2,7 +2,7 @@
using System.Diagnostics;
using Model = Discord.API.Connection;

namespace Discord.Rest
namespace Discord
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class Connection : IConnection


+ 0
- 8
src/Discord.Net/Common/Entities/Users/IDMUser.cs View File

@@ -1,8 +0,0 @@
namespace Discord
{
public interface IDMUser : IUser
{
/// <summary> Gets the private channel with this user. </summary>
IDMChannel Channel { get; }
}
}

+ 0
- 3
src/Discord.Net/Common/Entities/Users/IGuildUser.cs View File

@@ -29,9 +29,6 @@ namespace Discord
/// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary>
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>
Task Kick();
/// <summary> Modifies this user's properties in this guild. </summary>


+ 1
- 0
src/Discord.Net/Common/Entities/Users/IUser.cs View File

@@ -17,6 +17,7 @@ namespace Discord
/// <summary> Gets the username for this user. </summary>
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>
Task<IDMChannel> CreateDMChannel();
}


+ 3
- 3
src/Discord.Net/Common/MentionUtils.cs View File

@@ -64,11 +64,11 @@ namespace Discord
}

/// <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>
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>
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)
{
var matches = regex.Matches(text);


+ 0
- 243
src/Discord.Net/Discord.Net.csproj View File

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

+ 19
- 0
src/Discord.Net/Discord.Net.xproj View File

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

+ 1
- 2
src/Discord.Net/IDiscordClient.cs View File

@@ -10,7 +10,7 @@ namespace Discord
public interface IDiscordClient
{
TokenType AuthTokenType { get; }
DiscordAPIClient APIClient { get; }
DiscordApiClient ApiClient { get; }
IRestClient RestClient { get; }
IRequestQueue RequestQueue { get; }

@@ -36,6 +36,5 @@ namespace Discord

Task<IEnumerable<IVoiceRegion>> GetVoiceRegions();
Task<IVoiceRegion> GetVoiceRegion(string id);
Task<IVoiceRegion> GetOptimalVoiceRegion();
}
}

+ 50
- 38
src/Discord.Net/Net/Converters/DiscordContractResolver.cs View File

@@ -3,61 +3,73 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System.Reflection;

namespace Discord.Net.Converters
{
public class DiscordContractResolver : DefaultContractResolver
{
{
private static readonly TypeInfo _ienumerable = typeof(IEnumerable<ulong[]>).GetTypeInfo();

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization 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
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;
}


+ 1
- 1
src/Discord.Net/Net/Converters/OptionalConverter.cs View File

@@ -8,7 +8,7 @@ namespace Discord.Net.Converters
public class OptionalConverter : JsonConverter
{
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 CanRead => false;


+ 18
- 13
src/Discord.Net/Net/Rest/DefaultRestClient.cs View File

@@ -75,6 +75,7 @@ namespace Discord.Net.Rest
{
foreach (var p in multipartParams)
{
#if CSHARP7
switch (p.Value)
{
case string value:
@@ -92,6 +93,22 @@ namespace Discord.Net.Rest
default:
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;
@@ -101,21 +118,9 @@ namespace Discord.Net.Rest

private async Task<Stream> SendInternal(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly)
{
int retryCount = 0;
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;
if (statusCode < 200 || statusCode >= 300) //2xx = Success


+ 1
- 3
src/Discord.Net/Net/Rest/RestClientProvider.cs View File

@@ -1,6 +1,4 @@
using System.Threading;

namespace Discord.Net.Rest
namespace Discord.Net.Rest
{
public delegate IRestClient RestClientProvider(string baseUrl);
}

+ 11
- 0
src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs View File

@@ -0,0 +1,11 @@
using System;

namespace Discord.Net.WebSockets
{
public class BinaryMessageEventArgs : EventArgs
{
public byte[] Data { get; }

public BinaryMessageEventArgs(byte[] data) { }
}
}

+ 180
- 0
src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs View File

@@ -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) { }
});
}
}
}

+ 19
- 0
src/Discord.Net/Net/WebSockets/IWebSocketClient.cs View File

@@ -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);
}
}

+ 11
- 0
src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs View File

@@ -0,0 +1,11 @@
using System;

namespace Discord.Net.WebSockets
{
public class TextMessageEventArgs : EventArgs
{
public string Message { get; }

public TextMessageEventArgs(string msg) { Message = msg; }
}
}

+ 4
- 0
src/Discord.Net/Net/WebSockets/WebSocketProvider.cs View File

@@ -0,0 +1,4 @@
namespace Discord.Net.WebSockets
{
public delegate IWebSocketClient WebSocketProvider(string baseUrl);
}

+ 13
- 10
src/Discord.Net/Properties/AssemblyInfo.cs View File

@@ -1,16 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
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: 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")]

+ 35
- 39
src/Discord.Net/Rest/DiscordClient.cs View File

@@ -10,25 +10,28 @@ using System.Threading.Tasks;

namespace Discord.Rest
{
//TODO: Docstrings
//TODO: Log Internal/External REST Rate Limits, 502s
//TODO: Log Logins/Logouts
public sealed class DiscordClient : IDiscordClient, IDisposable
{
public event EventHandler<LogMessageEventArgs> Log;
public event EventHandler LoggedIn, LoggedOut;

private readonly Logger _discordLogger, _restLogger;
private readonly SemaphoreSlim _connectionLock;
private readonly RestClientProvider _restClientProvider;
private readonly LogManager _log;
private CancellationTokenSource _cancelTokenSource;
private bool _isDisposed;
private string _userAgent;
private SelfUser _currentUser;

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)
{
@@ -37,12 +40,14 @@ namespace Discord.Rest

_restClientProvider = config.RestClientProvider;

_connectionLock = new SemaphoreSlim(1, 1);
_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)
@@ -72,7 +77,7 @@ namespace Discord.Rest
_cancelTokenSource = new CancellationTokenSource();

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);
}
catch { await LogoutInternal().ConfigureAwait(false); throw; }
@@ -85,22 +90,20 @@ namespace Discord.Rest
{
_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);
}
catch { await LogoutInternal().ConfigureAwait(false); throw; }
}
private async Task CompleteLogin(bool validateToken)
{
APIClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms");

if (validateToken)
{
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;
@@ -127,7 +130,7 @@ namespace Discord.Rest
catch { }
}

await APIClient.Logout().ConfigureAwait(false);
await ApiClient.Logout().ConfigureAwait(false);
_currentUser = null;

if (wasLoggedIn)
@@ -139,18 +142,18 @@ namespace Discord.Rest

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));
}

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.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)
{
var guild = new Guild(this, guildModel);
@@ -164,13 +167,13 @@ namespace Discord.Rest
}
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));
}

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)
return new Invite(this, model);
return null;
@@ -178,41 +181,41 @@ namespace Discord.Rest

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)
return new Guild(this, model);
return null;
}
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)
return new GuildEmbed(model);
return null;
}
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));

}
public async Task<Guild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null)
{
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);
}

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)
return new PublicUser(this, model);
return null;
}
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)
return new PublicUser(this, model);
return null;
@@ -222,7 +225,7 @@ namespace Discord.Rest
var user = _currentUser;
if (user == null)
{
var model = await APIClient.GetCurrentUser().ConfigureAwait(false);
var model = await ApiClient.GetCurrentUser().ConfigureAwait(false);
user = new SelfUser(this, model);
_currentUser = user;
}
@@ -230,25 +233,20 @@ namespace Discord.Rest
}
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));
}

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));
}
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();
}
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)
{
@@ -261,7 +259,7 @@ namespace Discord.Rest
}
public void Dispose() => Dispose(true);

API.DiscordAPIClient IDiscordClient.APIClient => APIClient;
API.DiscordApiClient IDiscordClient.ApiClient => ApiClient;

async Task<IChannel> IDiscordClient.GetChannel(ulong id)
=> await GetChannel(id).ConfigureAwait(false);
@@ -289,7 +287,5 @@ namespace Discord.Rest
=> await GetVoiceRegions().ConfigureAwait(false);
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegion(string id)
=> await GetVoiceRegion(id).ConfigureAwait(false);
async Task<IVoiceRegion> IDiscordClient.GetOptimalVoiceRegion()
=> await GetOptimalVoiceRegion().ConfigureAwait(false);
}
}

+ 15
- 15
src/Discord.Net/Rest/Entities/Channels/DMChannel.cs View File

@@ -18,7 +18,7 @@ namespace Discord.Rest
internal DiscordClient Discord { get; }

/// <inheritdoc />
public DMUser Recipient { get; private set; }
public User Recipient { get; private set; }

/// <inheritdoc />
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
@@ -33,13 +33,13 @@ namespace Discord.Rest
private void Update(Model model)
{
if (Recipient == null)
Recipient = new DMUser(this, model.Recipient);
Recipient = new PublicUser(Discord, model.Recipient);
else
Recipient.Update(model.Recipient);
}

/// <inheritdoc />
public async Task<IUser> GetUser(ulong id)
public async Task<User> GetUser(ulong id)
{
var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false);
if (id == Recipient.Id)
@@ -50,24 +50,24 @@ namespace Discord.Rest
return null;
}
/// <inheritdoc />
public async Task<IEnumerable<IUser>> GetUsers()
public async Task<IEnumerable<User>> GetUsers()
{
var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false);
return ImmutableArray.Create<IUser>(currentUser, Recipient);
return ImmutableArray.Create(currentUser, Recipient);
}

/// <inheritdoc />
public async Task<IEnumerable<Message>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
{
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));
}
/// <inheritdoc />
public async Task<IEnumerable<Message>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
{
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));
}

@@ -75,7 +75,7 @@ namespace Discord.Rest
public async Task<Message> SendMessage(string text, bool isTTS = false)
{
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);
}
/// <inheritdoc />
@@ -85,7 +85,7 @@ namespace Discord.Rest
using (var file = File.OpenRead(filePath))
{
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);
}
}
@@ -93,32 +93,32 @@ namespace Discord.Rest
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 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);
}

/// <inheritdoc />
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 />
public async Task TriggerTyping()
{
await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
}

/// <inheritdoc />
public async Task Close()
{
await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
}

/// <inheritdoc />
public async Task Update()
{
var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false);
var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false);
Update(model);
}

@@ -126,7 +126,7 @@ namespace Discord.Rest
public override string ToString() => '@' + Recipient.ToString();
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)";

IDMUser IDMChannel.Recipient => Recipient;
IUser IDMChannel.Recipient => Recipient;
IEnumerable<IMessage> IMessageChannel.CachedMessages => Array.Empty<Message>();

async Task<IEnumerable<IUser>> IChannel.GetUsers()


+ 9
- 9
src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs View File

@@ -55,7 +55,7 @@ namespace Discord.Rest

var args = new ModifyGuildChannelParams();
func(args);
var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
Update(model);
}
@@ -78,7 +78,7 @@ namespace Discord.Rest
/// <summary> Downloads a collection of all invites to this channel. </summary>
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));
}

@@ -86,20 +86,20 @@ namespace Discord.Rest
public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms)
{
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 });
}
/// <inheritdoc />
public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms)
{
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 });
}
/// <inheritdoc />
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;
_overwrites.TryRemove(user.Id, out value);
@@ -107,7 +107,7 @@ namespace Discord.Rest
/// <inheritdoc />
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;
_overwrites.TryRemove(role.Id, out value);
@@ -127,19 +127,19 @@ namespace Discord.Rest
Temporary = isTemporary,
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);
}

/// <inheritdoc />
public async Task Delete()
{
await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task Update()
{
var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false);
var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false);
Update(model);
}



+ 8
- 8
src/Discord.Net/Rest/Entities/Channels/TextChannel.cs View File

@@ -35,7 +35,7 @@ namespace Discord.Rest

var args = new ModifyTextChannelParams();
func(args);
var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
Update(model);
}

@@ -64,14 +64,14 @@ namespace Discord.Rest
public async Task<IEnumerable<Message>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
{
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));
}
/// <inheritdoc />
public async Task<IEnumerable<Message>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
{
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));
}

@@ -79,7 +79,7 @@ namespace Discord.Rest
public async Task<Message> SendMessage(string text, bool isTTS = false)
{
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);
}
/// <inheritdoc />
@@ -89,7 +89,7 @@ namespace Discord.Rest
using (var file = File.OpenRead(filePath))
{
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);
}
}
@@ -97,20 +97,20 @@ namespace Discord.Rest
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 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);
}

/// <inheritdoc />
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 />
public async Task TriggerTyping()
{
await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
}

private string DebuggerDisplay => $"{Name} ({Id}, Text)";


+ 1
- 1
src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs View File

@@ -30,7 +30,7 @@ namespace Discord.Rest

var args = new ModifyVoiceChannelParams();
func(args);
var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
Update(model);
}



+ 25
- 28
src/Discord.Net/Rest/Entities/Guilds/Guild.cs View File

@@ -115,7 +115,7 @@ namespace Discord.Rest
/// <inheritdoc />
public async Task Update()
{
var response = await Discord.APIClient.GetGuild(Id).ConfigureAwait(false);
var response = await Discord.ApiClient.GetGuild(Id).ConfigureAwait(false);
Update(response);
}
/// <inheritdoc />
@@ -125,7 +125,7 @@ namespace Discord.Rest

var args = new ModifyGuildParams();
func(args);
var model = await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false);
var model = await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false);
Update(model);
}
/// <inheritdoc />
@@ -135,35 +135,35 @@ namespace Discord.Rest

var args = new ModifyGuildEmbedParams();
func(args);
var model = await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false);
var model = await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false);
Update(model);
}
/// <inheritdoc />
public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args)
{
await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public async Task Leave()
{
await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false);
await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task Delete()
{
await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false);
}

/// <inheritdoc />
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));
}
/// <inheritdoc />
@@ -171,24 +171,21 @@ namespace Discord.Rest
/// <inheritdoc />
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 />
public Task RemoveBan(IUser user) => RemoveBan(user.Id);
/// <inheritdoc />
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>
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)
return ToChannel(model);
return null;
@@ -196,7 +193,7 @@ namespace Discord.Rest
/// <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);
var models = await Discord.ApiClient.GetGuildChannels(Id).ConfigureAwait(false);
return models.Select(x => ToChannel(x));
}
/// <summary> Creates a new text channel. </summary>
@@ -205,7 +202,7 @@ namespace Discord.Rest
if (name == null) throw new ArgumentNullException(nameof(name));

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);
}
/// <summary> Creates a new voice channel. </summary>
@@ -214,28 +211,28 @@ namespace Discord.Rest
if (name == null) throw new ArgumentNullException(nameof(name));

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);
}

/// <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);
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>
public async Task<GuildIntegration> CreateIntegration(ulong id, string 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);
}

/// <summary> Gets a collection of all invites to this guild. </summary>
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));
}
/// <summary> Creates a new invite to this guild. </summary>
@@ -251,7 +248,7 @@ namespace Discord.Rest
Temporary = isTemporary,
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);
}

@@ -269,7 +266,7 @@ namespace Discord.Rest
{
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);

await role.Modify(x =>
@@ -287,20 +284,20 @@ namespace Discord.Rest
public async Task<IEnumerable<GuildUser>> GetUsers()
{
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));
}
/// <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);
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>
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)
return new GuildUser(this, model);
return null;
@@ -316,9 +313,9 @@ namespace Discord.Rest
var args = new GuildPruneParams() { Days = days };
GetGuildPruneCountResponse model;
if (simulate)
model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false);
model = await Discord.ApiClient.GetGuildPruneCount(Id, args).ConfigureAwait(false);
else
model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false);
model = await Discord.ApiClient.BeginGuildPrune(Id, args).ConfigureAwait(false);
return model.Pruned;
}



+ 3
- 3
src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs View File

@@ -60,7 +60,7 @@ namespace Discord.Rest
/// <summary> </summary>
public async Task Delete()
{
await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false);
}
/// <summary> </summary>
public async Task Modify(Action<ModifyGuildIntegrationParams> func)
@@ -69,14 +69,14 @@ namespace Discord.Rest

var args = new ModifyGuildIntegrationParams();
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);
}
/// <summary> </summary>
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;


+ 2
- 2
src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs View File

@@ -42,12 +42,12 @@ namespace Discord
/// <inheritdoc />
public async Task Leave()
{
await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false);
await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task Delete()
{
await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false);
}

public override string ToString() => Name;


+ 27
- 32
src/Discord.Net/Rest/Entities/Message.cs View File

@@ -28,14 +28,14 @@ namespace Discord.Rest
/// <inheritdoc />
public IMessageChannel Channel { get; }
/// <inheritdoc />
public User Author { get; }
public IUser Author { get; }

/// <inheritdoc />
public IReadOnlyList<Attachment> Attachments { get; private set; }
/// <inheritdoc />
public IReadOnlyList<Embed> Embeds { get; private set; }
/// <inheritdoc />
public IReadOnlyList<PublicUser> MentionedUsers { get; private set; }
public IReadOnlyList<IUser> MentionedUsers { get; private set; }
/// <inheritdoc />
public IReadOnlyList<ulong> MentionedChannelIds { get; private set; }
/// <inheritdoc />
@@ -55,6 +55,10 @@ namespace Discord.Rest
}
private void Update(Model model)
{
var guildChannel = Channel as GuildChannel;
var guild = guildChannel?.Guild;
var discord = Discord;

IsTTS = model.IsTextToSpeech;
Timestamp = model.Timestamp;
EditedTimestamp = model.EditedTimestamp;
@@ -80,38 +84,32 @@ namespace Discord.Rest
else
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++)
builder.Add(new PublicUser(discord, model.Mentions[i]));
MentionedUsers = builder.ToArray();
mentions[i] = new PublicUser(discord, model.Mentions[i]);
MentionedUsers = ImmutableArray.Create(mentions);
}
else
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);

Author.Update(model.Author);
}

/// <inheritdoc />
@@ -125,9 +123,9 @@ namespace Discord.Rest

Model model;
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
model = await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false);
model = await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false);
Update(model);
}

@@ -136,18 +134,15 @@ namespace Discord.Rest
{
var guildChannel = Channel as GuildChannel;
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
await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false);
}

public override string ToString() => Text;
private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}";

IUser IMessage.Author => Author;
IReadOnlyList<Attachment> IMessage.Attachments => Attachments;
IReadOnlyList<Embed> IMessage.Embeds => Embeds;
IReadOnlyList<ulong> IMessage.MentionedChannelIds => MentionedChannelIds;
IReadOnlyList<IUser> IMessage.MentionedUsers => MentionedUsers;
}
}

+ 4
- 4
src/Discord.Net/Rest/Entities/Role.cs View File

@@ -60,12 +60,12 @@ namespace Discord.Rest

var args = new ModifyGuildRoleParams();
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);
}
/// <summary> Deletes this message. </summary>
public async Task Delete()
=> await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);
=> await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);

/// <inheritdoc />
public override string ToString() => Name;
@@ -75,8 +75,8 @@ namespace Discord.Rest
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));
}
}


+ 0
- 20
src/Discord.Net/Rest/Entities/Users/DMUser.cs View File

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

+ 4
- 14
src/Discord.Net/Rest/Entities/Users/GuildUser.cs View File

@@ -55,23 +55,13 @@ namespace Discord.Rest

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);
}

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);
await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false);
}

public ChannelPermissions GetPermissions(IGuildChannel channel)
@@ -91,13 +81,13 @@ namespace Discord.Rest
if (isCurrentUser && args.Nickname.IsSpecified)
{
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
}

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)
IsDeaf = args.Deaf.Value;
if (args.Mute.IsSpecified)


+ 2
- 2
src/Discord.Net/Rest/Entities/Users/SelfUser.cs View File

@@ -30,7 +30,7 @@ namespace Discord.Rest
/// <inheritdoc />
public async Task Update()
{
var model = await Discord.APIClient.GetCurrentUser().ConfigureAwait(false);
var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false);
Update(model);
}

@@ -41,7 +41,7 @@ namespace Discord.Rest

var args = new ModifyCurrentUserParams();
func(args);
var model = await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false);
var model = await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false);
Update(model);
}
}


+ 3
- 3
src/Discord.Net/Rest/Entities/Users/User.cs View File

@@ -45,10 +45,10 @@ namespace Discord.Rest
Username = model.Username;
}

public async Task<DMChannel> CreateDMChannel()
protected virtual async Task<DMChannel> CreateDMChannelInternal()
{
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);
}
@@ -63,6 +63,6 @@ namespace Discord.Rest

/// <inheritdoc />
async Task<IDMChannel> IUser.CreateDMChannel()
=> await CreateDMChannel().ConfigureAwait(false);
=> await CreateDMChannelInternal().ConfigureAwait(false);
}
}

+ 238
- 72
src/Discord.Net/WebSocket/DiscordClient.cs View File

@@ -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.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Discord.API;
using Discord.Net.Rest;

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));
}
}

+ 39
- 0
src/Discord.Net/WebSocket/DiscordSocketConfig.cs View File

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

+ 37
- 0
src/Discord.Net/WebSocket/Entities/Channels/Channel.cs View File

@@ -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));
}
}

+ 27
- 31
src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs View File

@@ -1,5 +1,4 @@
using Discord.API.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
@@ -11,41 +10,34 @@ using Model = Discord.API.Channel;
namespace Discord.WebSocket
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class DMChannel : IDMChannel
public class DMChannel : Channel, IDMChannel
{
private readonly MessageCache _messages;

/// <inheritdoc />
public ulong Id { get; }
internal DiscordClient Discord { get; }

/// <inheritdoc />
public DMUser Recipient { get; private set; }

/// <inheritdoc />
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
public User Recipient { get; private set; }
/// <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;

internal DMChannel(DiscordClient discord, Model model)
internal DMChannel(DiscordClient discord, User recipient, Model model)
: base(model.Id)
{
Id = model.Id;
Discord = discord;
Recipient = recipient;
_messages = new MessageCache(Discord, this);

Update(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)
return Recipient;
@@ -54,6 +46,10 @@ namespace Discord.WebSocket
else
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>
public Message GetCachedMessage(ulong id)
@@ -75,8 +71,8 @@ namespace Discord.WebSocket
public async Task<Message> SendMessage(string text, bool isTTS = false)
{
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 />
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))
{
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 />
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 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 />
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 />
public async Task TriggerTyping()
{
await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
}

/// <inheritdoc />
public async Task Close()
{
await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
}

/// <inheritdoc />
public override string ToString() => '@' + Recipient.ToString();
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)";

IDMUser IDMChannel.Recipient => Recipient;
IUser IDMChannel.Recipient => Recipient;
IEnumerable<IMessage> IMessageChannel.CachedMessages => CachedMessages;

Task<IEnumerable<IUser>> IChannel.GetUsers()
=> Task.FromResult(Users);
=> Task.FromResult<IEnumerable<IUser>>(Users);
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.FromResult(GetUser(id));
=> Task.FromResult<IUser>(GetUser(id));
Task<IMessage> IMessageChannel.GetCachedMessage(ulong id)
=> Task.FromResult<IMessage>(GetCachedMessage(id));
async Task<IEnumerable<IMessage>> IMessageChannel.GetMessages(int limit)


+ 22
- 18
src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs View File

@@ -8,13 +8,11 @@ using Model = Discord.API.Channel;

namespace Discord.WebSocket
{
public abstract class GuildChannel : IGuildChannel
public abstract class GuildChannel : Channel, IGuildChannel
{
private ConcurrentDictionary<ulong, Overwrite> _overwrites;
internal PermissionsCache _permissions;

/// <inheritdoc />
public ulong Id { get; }
/// <summary> Gets the guild this channel is a member of. </summary>
public Guild Guild { get; }

@@ -22,17 +20,15 @@ namespace Discord.WebSocket
public string Name { get; private set; }
/// <inheritdoc />
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 />
public IReadOnlyDictionary<ulong, Overwrite> PermissionOverwrites => _overwrites;
internal DiscordClient Discord => Guild.Discord;

internal GuildChannel(Guild guild, Model model)
: base(model.Id)
{
Id = model.Id;
Guild = guild;

Update(model);
@@ -57,11 +53,19 @@ namespace Discord.WebSocket

var args = new ModifyGuildChannelParams();
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>
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 />
public OverwritePermissions? GetPermissionOverwrite(IUser user)
@@ -82,7 +86,7 @@ namespace Discord.WebSocket
/// <summary> Downloads a collection of all invites to this channel. </summary>
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));
}

@@ -90,23 +94,23 @@ namespace Discord.WebSocket
public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms)
{
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 />
public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms)
{
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 />
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 />
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>
@@ -123,14 +127,14 @@ namespace Discord.WebSocket
Temporary = isTemporary,
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);
}

/// <inheritdoc />
public async Task Delete()
{
await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
}

/// <inheritdoc />


+ 9
- 9
src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs View File

@@ -42,7 +42,7 @@ namespace Discord.WebSocket

var args = new ModifyTextChannelParams();
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>
@@ -73,8 +73,8 @@ namespace Discord.WebSocket
public async Task<Message> SendMessage(string text, bool isTTS = false)
{
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 />
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))
{
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 />
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 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 />
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 />
public async Task TriggerTyping()
{
await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
}

private string DebuggerDisplay => $"{Name} ({Id}, Text)";


+ 1
- 1
src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs View File

@@ -34,7 +34,7 @@ namespace Discord.WebSocket

var args = new ModifyVoiceChannelParams();
func(args);
await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
}

public override GuildUser GetUser(ulong id)


+ 89
- 110
src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs View File

@@ -6,18 +6,21 @@ using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Guild;
using EmbedModel = Discord.API.GuildEmbed;
using RoleModel = Discord.API.Role;
using System.Diagnostics;

namespace Discord.WebSocket
{
/// <summary> Represents a Discord guild (called a server in the official client). </summary>
[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 ulong _ownerId;
private ulong? _afkChannelId, _embedChannelId;
private string _iconId, _splashId;
private int _userCount;

/// <inheritdoc />
public ulong Id { get; }
@@ -31,34 +34,52 @@ namespace Discord.WebSocket
public bool IsEmbeddable { get; private set; }
/// <inheritdoc />
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 />
public string VoiceRegionId { get; private set; }
public VoiceRegion VoiceRegion { get; private set; }
/// <inheritdoc />
public IReadOnlyList<Emoji> Emojis { get; private set; }
/// <inheritdoc />
public IReadOnlyList<string> Features { get; private set; }
/// <inheritdoc />
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);

/// <inheritdoc />
public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId);
/// <inheritdoc />
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 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>
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)
{
@@ -69,15 +90,15 @@ namespace Discord.WebSocket
}
private void Update(Model model)
{
AFKChannelId = model.AFKChannelId;
_afkChannelId = model.AFKChannelId;
AFKTimeout = model.AFKTimeout;
EmbedChannelId = model.EmbedChannelId;
_embedChannelId = model.EmbedChannelId;
IsEmbeddable = model.EmbedEnabled;
Features = model.Features;
_iconId = model.Icon;
Name = model.Name;
OwnerId = model.OwnerId;
VoiceRegionId = model.Region;
_ownerId = model.OwnerId;
VoiceRegion = Discord.GetVoiceRegion(model.Region);
_splashId = model.Splash;
VerificationLevel = model.VerificationLevel;

@@ -99,20 +120,6 @@ namespace Discord.WebSocket
}
_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 />
public async Task Modify(Action<ModifyGuildParams> func)
@@ -121,7 +128,7 @@ namespace Discord.WebSocket

var args = new ModifyGuildParams();
func(args);
await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task ModifyEmbed(Action<ModifyGuildEmbedParams> func)
@@ -130,75 +137,66 @@ namespace Discord.WebSocket

var args = new ModifyGuildEmbedParams();
func(args);
await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args)
{
await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task ModifyRoles(IEnumerable<ModifyGuildRolesParams> args)
{
await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuildRoles(Id, args).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task Leave()
{
await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false);
await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task Delete()
{
await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false);
}

/// <inheritdoc />
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 />
public Task AddBan(IUser user, int pruneDays = 0) => AddBan(user, pruneDays);
/// <inheritdoc />
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 />
public Task RemoveBan(IUser user) => RemoveBan(user.Id);
/// <inheritdoc />
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>
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;
}
/// <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>
public async Task<TextChannel> CreateTextChannel(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));

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);
}
/// <summary> Creates a new voice channel. </summary>
@@ -207,28 +205,22 @@ namespace Discord.WebSocket
if (name == null) throw new ArgumentNullException(nameof(name));

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);
}

/// <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>
public async Task<GuildIntegration> CreateIntegration(ulong id, string 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);
}

/// <summary> Gets a collection of all invites to this guild. </summary>
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));
}
/// <summary> Creates a new invite to this guild. </summary>
@@ -244,7 +236,7 @@ namespace Discord.WebSocket
Temporary = isTemporary,
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);
}

@@ -262,7 +254,7 @@ namespace Discord.WebSocket
{
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);

await role.Modify(x =>
@@ -275,43 +267,23 @@ namespace Discord.WebSocket

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>
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;
}
/// <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)
{
var args = new GuildPruneParams() { Days = days };
GetGuildPruneCountResponse model;
if (simulate)
model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false);
model = await Discord.ApiClient.GetGuildPruneCount(Id, args).ConfigureAwait(false);
else
model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false);
model = await Discord.ApiClient.BeginGuildPrune(Id, args).ConfigureAwait(false);
return model.Pruned;
}

@@ -331,15 +303,22 @@ namespace Discord.WebSocket
private string DebuggerDisplay => $"{Name} ({Id})";

IEnumerable<Emoji> IGuild.Emojis => Emojis;
ulong IGuild.EveryoneRoleId => EveryoneRole.Id;
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()
=> 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)
=> await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false);
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<IEnumerable<IRole>> IGuild.GetRoles()
=> 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.CompletedTask;
}


+ 5
- 5
src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs View File

@@ -31,7 +31,7 @@ namespace Discord.WebSocket
/// <inheritdoc />
public Role Role { get; private set; }
/// <inheritdoc />
public User User { get; private set; }
public GuildUser User { get; private set; }
/// <inheritdoc />
public IntegrationAccount Account { get; private set; }
internal DiscordClient Discord => Guild.Discord;
@@ -54,13 +54,13 @@ namespace Discord.WebSocket
SyncedAt = model.SyncedAt;

Role = Guild.GetRole(model.RoleId);
User = new PublicUser(Discord, model.User);
User = Guild.GetUser(model.User.Id);
}

/// <summary> </summary>
public async Task Delete()
{
await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false);
}
/// <summary> </summary>
public async Task Modify(Action<ModifyGuildIntegrationParams> func)
@@ -69,12 +69,12 @@ namespace Discord.WebSocket

var args = new ModifyGuildIntegrationParams();
func(args);
await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false);
}
/// <summary> </summary>
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;


+ 39
- 35
src/Discord.Net/WebSocket/Entities/Message.cs View File

@@ -3,11 +3,13 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Message;

namespace Discord.WebSocket
{
//TODO: Support mention_roles
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class Message : IMessage
{
@@ -28,33 +30,36 @@ namespace Discord.WebSocket
/// <inheritdoc />
public IMessageChannel Channel { get; }
/// <inheritdoc />
public User Author { get; }
public IUser Author { get; }

/// <inheritdoc />
public IReadOnlyList<Attachment> Attachments { get; private set; }
/// <inheritdoc />
public IReadOnlyList<Embed> Embeds { get; private set; }
/// <inheritdoc />
public IReadOnlyList<PublicUser> MentionedUsers { get; private set; }
public IReadOnlyList<GuildUser> MentionedUsers { get; private set; }
/// <inheritdoc />
public IReadOnlyList<ulong> MentionedChannelIds { get; private set; }
public IReadOnlyList<GuildChannel> MentionedChannels { get; private set; }
/// <inheritdoc />
public IReadOnlyList<ulong> MentionedRoleIds { get; private set; }
public IReadOnlyList<Role> MentionedRoles { get; private set; }

/// <inheritdoc />
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
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;
Channel = channel;
Author = new PublicUser(Discord, model.Author);
Author = author;

Update(model);
}
private void Update(Model model)
{
var guildChannel = Channel as GuildChannel;
var guild = guildChannel?.Guild;

IsTTS = model.IsTextToSpeech;
Timestamp = model.Timestamp;
EditedTimestamp = model.EditedTimestamp;
@@ -80,38 +85,38 @@ namespace Discord.WebSocket
else
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++)
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();
}
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);

Author.Update(model.Author);
//Author.Update(model.Author); //TODO: Uncomment this somehow
}

/// <inheritdoc />
@@ -124,9 +129,9 @@ namespace Discord.WebSocket
var guildChannel = Channel as GuildChannel;
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
await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false);
await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false);
}

/// <inheritdoc />
@@ -134,18 +139,17 @@ namespace Discord.WebSocket
{
var guildChannel = Channel as GuildChannel;
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
await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false);
await Discord.ApiClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false);
}

public override string ToString() => Text;
private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}";

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<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray();
IReadOnlyList<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray();
}
}

+ 6
- 9
src/Discord.Net/WebSocket/Entities/Role.cs View File

@@ -35,6 +35,7 @@ namespace Discord.WebSocket
public bool IsEveryone => Id == Guild.Id;
/// <inheritdoc />
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 Role(Guild guild, Model model)
@@ -60,23 +61,19 @@ namespace Discord.WebSocket

var args = new ModifyGuildRoleParams();
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>
public async Task Delete()
=> await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);
=> await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);

/// <inheritdoc />
public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({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);
}
}

+ 0
- 20
src/Discord.Net/WebSocket/Entities/Users/DMUser.cs View File

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

+ 51
- 32
src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs View File

@@ -8,11 +8,12 @@ using Model = Discord.API.GuildMember;

namespace Discord.WebSocket
{
public class GuildUser : User, IGuildUser
public class GuildUser : IGuildUser
{
private ImmutableArray<Role> _roles;

public Guild Guild { get; }
public User GlobalUser { get; }

/// <inheritdoc />
public bool IsDeaf { get; private set; }
@@ -23,18 +24,38 @@ namespace Discord.WebSocket
/// <inheritdoc />
public string Nickname { get; private set; }
/// <inheritdoc />
public UserStatus Status { get; private set; }
/// <inheritdoc />
public Game? CurrentGame { get; private set; }
/// <inheritdoc />
public VoiceChannel VoiceChannel { get; private set; }
/// <inheritdoc />
public GuildPermissions GuildPermissions { get; private set; }

/// <inheritdoc />
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;

globalUser.Update(model.User);
Update(model);
}
internal void Update(Model model)
@@ -57,31 +78,6 @@ namespace Discord.WebSocket
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)
{
if (func == null) throw new NullReferenceException(nameof(func));
@@ -89,17 +85,17 @@ namespace Discord.WebSocket
var args = new ModifyGuildMemberParams();
func(args);

bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id;
bool isCurrentUser = Discord.CurrentUser.Id == Id;
if (isCurrentUser && args.Nickname.IsSpecified)
{
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
}

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)
IsDeaf = args.Deaf.Value;
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;
IReadOnlyList<IRole> IGuildUser.Roles => Roles;
@@ -118,7 +134,10 @@ namespace Discord.WebSocket

ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel)
=> GetPermissions(channel);
async Task<IDMChannel> IUser.CreateDMChannel()
=> await CreateDMChannel().ConfigureAwait(false);
Task IUpdateable.Update()
=> Task.CompletedTask;

}
}

+ 0
- 15
src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs View File

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

+ 2
- 5
src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs View File

@@ -7,17 +7,14 @@ namespace Discord.WebSocket
{
public class SelfUser : User, ISelfUser
{
internal override DiscordClient Discord { get; }

/// <inheritdoc />
public string Email { get; private set; }
/// <inheritdoc />
public bool IsVerified { get; private set; }

internal SelfUser(DiscordClient discord, Model model)
: base(model)
: base(discord, model)
{
Discord = discord;
}
internal override void Update(Model model)
{
@@ -34,7 +31,7 @@ namespace Discord.WebSocket

var args = new ModifyCurrentUserParams();
func(args);
await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false);
await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false);
}

Task IUpdateable.Update()


+ 16
- 6
src/Discord.Net/WebSocket/Entities/Users/User.cs View File

@@ -6,14 +6,15 @@ using Model = Discord.API.User;

namespace Discord.WebSocket
{
//TODO: Unload when there are no more references via DMUser or GuildUser
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public abstract class User : IUser
public class User : IUser
{
private string _avatarId;

/// <inheritdoc />
public ulong Id { get; }
internal abstract DiscordClient Discord { get; }
internal DiscordClient Discord { get; }

/// <inheritdoc />
public ushort Discriminator { get; private set; }
@@ -21,6 +22,8 @@ namespace Discord.WebSocket
public bool IsBot { get; private set; }
/// <inheritdoc />
public string Username { get; private set; }
/// <inheritdoc />
public DMChannel DMChannel { get; private set; }

/// <inheritdoc />
public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId);
@@ -31,8 +34,9 @@ namespace Discord.WebSocket
/// <inheritdoc />
public string NicknameMention => MentionUtils.Mention(this, true);

internal User(Model model)
internal User(DiscordClient discord, Model model)
{
Discord = discord;
Id = model.Id;

Update(model);
@@ -47,10 +51,16 @@ namespace Discord.WebSocket

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}";


+ 14
- 0
src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class ChannelEventArgs : EventArgs
{
public IChannel Channel { get; }

public ChannelEventArgs(IChannel channel)
{
Channel = channel;
}
}
}

+ 14
- 0
src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs View File

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

+ 14
- 0
src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class CurrentUserEventArgs : EventArgs
{
public SelfUser CurrentUser { get; }

public CurrentUserEventArgs(SelfUser currentUser)
{
CurrentUser = currentUser;
}
}
}

+ 14
- 0
src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs View File

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

+ 16
- 0
src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs View File

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

+ 14
- 0
src/Discord.Net/WebSocket/Events/GuildEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class GuildEventArgs : EventArgs
{
public Guild Guild { get; }

public GuildEventArgs(Guild guild)
{
Guild = guild;
}
}
}

+ 14
- 0
src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs View File

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

+ 14
- 0
src/Discord.Net/WebSocket/Events/MessageEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class MessageEventArgs : EventArgs
{
public Message Message { get; }

public MessageEventArgs(Message message)
{
Message = message;
}
}
}

+ 14
- 0
src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs View File

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

+ 14
- 0
src/Discord.Net/WebSocket/Events/RoleEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class RoleEventArgs : EventArgs
{
public Role Role { get; }

public RoleEventArgs(Role role)
{
Role = role;
}
}
}

+ 14
- 0
src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs View File

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

+ 16
- 0
src/Discord.Net/WebSocket/Events/TypingEventArgs.cs View File

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

+ 14
- 0
src/Discord.Net/WebSocket/Events/UserEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class UserEventArgs : EventArgs
{
public IUser User { get; }

public UserEventArgs(IUser user)
{
User = user;
}
}
}

+ 14
- 0
src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs View File

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

+ 14
- 0
src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace Discord.WebSocket
{
public class VoiceChannelEventArgs : EventArgs
{
public VoiceChannel Channel { get; }

public VoiceChannelEventArgs(VoiceChannel channel)
{
Channel = channel;
}
}
}

+ 3
- 2
src/Discord.Net/WebSocket/MessageCache.cs View File

@@ -99,8 +99,9 @@ namespace Discord.WebSocket
RelativeDirection = dir,
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();
}
}
}


+ 32
- 8
src/Discord.Net/project.json View File

@@ -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": {
"NETStandard.Library": "1.5.0-rc2-24027",
"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": {
"net461": { }
},

"runtimes": {
"win": { }
"netstandard1.3": {
"imports": [
"dotnet5.4",
"dnxcore50",
"portable-net45+win8"
]
}
}
}
}

Loading…
Cancel
Save