Browse Source

Started converting websocket and rpc classes

tags/1.0-rc
RogueException 8 years ago
parent
commit
dd86f03306
100 changed files with 1685 additions and 1715 deletions
  1. +0
    -5
      Discord.Net.sln
  2. +7
    -0
      src/Discord.Net.Core/AssemblyInfo.cs
  3. +2
    -2
      src/Discord.Net.Core/Entities/Guilds/Emoji.cs
  4. +2
    -2
      src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs
  5. +2
    -2
      src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs
  6. +2
    -2
      src/Discord.Net.Core/Entities/Users/Game.cs
  7. +2
    -0
      src/Discord.Net.Core/Entities/Users/UserStatus.cs
  8. +0
    -2
      src/Discord.Net.Core/Format.cs
  9. +0
    -2
      src/Discord.Net.Core/IDiscordClient.cs
  10. +19
    -0
      src/Discord.Net.Core/Logging/LogManager.cs
  11. +0
    -0
      src/Discord.Net.Core/Logging/Logger.cs
  12. +10
    -0
      src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs
  13. +0
    -0
      src/Discord.Net.Core/Utils/AsyncEvent.cs
  14. +0
    -0
      src/Discord.Net.Core/Utils/ConcurrentHashSet.cs
  15. +0
    -0
      src/Discord.Net.Core/Utils/DateTimeUtils.cs
  16. +0
    -0
      src/Discord.Net.Core/Utils/Extensions/CollectionExtensions.cs
  17. +0
    -0
      src/Discord.Net.Core/Utils/Extensions/Permissions.cs
  18. +0
    -0
      src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs
  19. +0
    -0
      src/Discord.Net.Core/Utils/MentionsHelper.cs
  20. +0
    -0
      src/Discord.Net.Core/Utils/Paging/Page.cs
  21. +0
    -0
      src/Discord.Net.Core/Utils/Paging/PageInfo.cs
  22. +0
    -0
      src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs
  23. +0
    -0
      src/Discord.Net.Core/Utils/Permissions.cs
  24. +0
    -0
      src/Discord.Net.Core/Utils/Preconditions.cs
  25. +6
    -0
      src/Discord.Net.Rest/AssemblyInfo.cs
  26. +118
    -0
      src/Discord.Net.Rest/ClientHelper.cs
  27. +162
    -0
      src/Discord.Net.Rest/DiscordClient.cs
  28. +47
    -266
      src/Discord.Net.Rest/DiscordRestClient.cs
  29. +22
    -22
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  30. +48
    -0
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
  31. +12
    -9
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  32. +5
    -5
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  33. +5
    -5
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  34. +2
    -2
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  35. +2
    -2
      src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
  36. +21
    -21
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  37. +1
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestBan.cs
  38. +2
    -2
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  39. +2
    -2
      src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs
  40. +2
    -2
      src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs
  41. +2
    -2
      src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs
  42. +3
    -3
      src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs
  43. +2
    -2
      src/Discord.Net.Rest/Entities/Invites/RestInvite.cs
  44. +2
    -2
      src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs
  45. +5
    -5
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  46. +1
    -0
      src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs
  47. +2
    -2
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  48. +2
    -2
      src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs
  49. +2
    -2
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  50. +2
    -2
      src/Discord.Net.Rest/Entities/RestApplication.cs
  51. +2
    -2
      src/Discord.Net.Rest/Entities/RestEntity.cs
  52. +2
    -2
      src/Discord.Net.Rest/Entities/Roles/RestRole.cs
  53. +2
    -2
      src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs
  54. +2
    -2
      src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs
  55. +2
    -2
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  56. +2
    -2
      src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs
  57. +2
    -2
      src/Discord.Net.Rest/Entities/Users/RestUser.cs
  58. +7
    -7
      src/Discord.Net.Rest/Entities/Users/UserHelper.cs
  59. +0
    -6
      src/Discord.Net.Rest/project.json
  60. +3
    -2
      src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs
  61. +3
    -0
      src/Discord.Net.Rpc/AssemblyInfo.cs
  62. +7
    -18
      src/Discord.Net.Rpc/DiscordRpcClient.cs
  63. +0
    -8
      src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs
  64. +10
    -0
      src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs
  65. +19
    -0
      src/Discord.Net.Rpc/Entities/RpcEntity.cs
  66. +2
    -2
      src/Discord.Net.Rpc/Entities/RpcGuild.cs
  67. +0
    -15
      src/Discord.Net.Rpc/Entities/RpcMessage.cs
  68. +21
    -5
      src/Discord.Net.Rpc/project.json
  69. +0
    -26
      src/Discord.Net.Utils/Discord.Net.Utils.projitems
  70. +0
    -13
      src/Discord.Net.Utils/Discord.Net.Utils.shproj
  71. +3
    -2
      src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs
  72. +3
    -0
      src/Discord.Net.WebSocket/AssemblyInfo.cs
  73. +4
    -23
      src/Discord.Net.WebSocket/Audio/AudioClient.cs
  74. +1
    -1
      src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
  75. +1
    -1
      src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
  76. +1
    -1
      src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs
  77. +1
    -1
      src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs
  78. +11
    -11
      src/Discord.Net.WebSocket/DataStore.cs
  79. +2
    -4
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj
  80. +105
    -163
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  81. +45
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs
  82. +102
    -41
      src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
  83. +98
    -105
      src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
  84. +84
    -82
      src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
  85. +82
    -54
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  86. +28
    -32
      src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
  87. +188
    -385
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  88. +0
    -76
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs
  89. +0
    -13
      src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs
  90. +17
    -24
      src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs
  91. +64
    -0
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  92. +15
    -8
      src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs
  93. +121
    -8
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  94. +19
    -0
      src/Discord.Net.WebSocket/Entities/SocketEntity.cs
  95. +0
    -9
      src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs
  96. +0
    -17
      src/Discord.Net.WebSocket/Entities/Users/Presence.cs
  97. +0
    -46
      src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs
  98. +4
    -55
      src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
  99. +18
    -24
      src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
  100. +56
    -35
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs

+ 0
- 5
Discord.Net.sln View File

@@ -15,8 +15,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Dis
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}"
EndProject EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Discord.Net.Utils\Discord.Net.Utils.shproj", "{2B75119C-9893-4AAA-8D38-6176EEB09060}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}"
@@ -24,9 +22,6 @@ EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}"
EndProject EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU


+ 7
- 0
src/Discord.Net.Core/AssemblyInfo.cs View File

@@ -0,0 +1,7 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Discord.Net.Rest")]
[assembly: InternalsVisibleTo("Discord.Net.Rpc")]
[assembly: InternalsVisibleTo("Discord.Net.WebSocket")]
[assembly: InternalsVisibleTo("Discord.Net.Commands")]
[assembly: InternalsVisibleTo("Discord.Net.Test")]

+ 2
- 2
src/Discord.Net.Core/Entities/Guilds/Emoji.cs View File

@@ -14,7 +14,7 @@ namespace Discord
public bool RequireColons { get; } public bool RequireColons { get; }
public IReadOnlyList<ulong> RoleIds { get; } public IReadOnlyList<ulong> RoleIds { get; }


public Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds)
private Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds)
{ {
Id = id; Id = id;
Name = name; Name = name;
@@ -22,7 +22,7 @@ namespace Discord
RequireColons = requireColons; RequireColons = requireColons;
RoleIds = roleIds; RoleIds = roleIds;
} }
public static Emoji Create(Model model)
internal static Emoji Create(Model model)
{ {
return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles));
} }


+ 2
- 2
src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs View File

@@ -7,12 +7,12 @@ namespace Discord
public string Name { get; } public string Name { get; }
public string Url { get; } public string Url { get; }


public EmbedProvider(string name, string url)
private EmbedProvider(string name, string url)
{ {
Name = name; Name = name;
Url = url; Url = url;
} }
public static EmbedProvider Create(Model model)
internal static EmbedProvider Create(Model model)
{ {
return new EmbedProvider(model.Name, model.Url); return new EmbedProvider(model.Name, model.Url);
} }


+ 2
- 2
src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs View File

@@ -9,14 +9,14 @@ namespace Discord
public int? Height { get; } public int? Height { get; }
public int? Width { get; } public int? Width { get; }


public EmbedThumbnail(string url, string proxyUrl, int? height, int? width)
private EmbedThumbnail(string url, string proxyUrl, int? height, int? width)
{ {
Url = url; Url = url;
ProxyUrl = proxyUrl; ProxyUrl = proxyUrl;
Height = height; Height = height;
Width = width; Width = width;
} }
public static EmbedThumbnail Create(Model model)
internal static EmbedThumbnail Create(Model model)
{ {
return new EmbedThumbnail(model.Url, model.ProxyUrl, return new EmbedThumbnail(model.Url, model.ProxyUrl,
model.Height.IsSpecified ? model.Height.Value : (int?)null, model.Height.IsSpecified ? model.Height.Value : (int?)null,


+ 2
- 2
src/Discord.Net.Core/Entities/Users/Game.cs View File

@@ -16,9 +16,9 @@ namespace Discord
StreamUrl = streamUrl; StreamUrl = streamUrl;
StreamType = type; StreamType = type;
} }
public Game(string name)
private Game(string name)
: this(name, null, StreamType.NotStreaming) { } : this(name, null, StreamType.NotStreaming) { }
public static Game Create(Model model)
internal static Game Create(Model model)
{ {
return new Game(model.Name, return new Game(model.Name,
model.StreamUrl.GetValueOrDefault(null), model.StreamUrl.GetValueOrDefault(null),


+ 2
- 0
src/Discord.Net.Core/Entities/Users/UserStatus.cs View File

@@ -5,6 +5,8 @@
Unknown, Unknown,
Online, Online,
Idle, Idle,
DoNotDisturb,
Invisible,
Offline Offline
} }
} }

+ 0
- 2
src/Discord.Net.Core/Format.cs View File

@@ -27,9 +27,7 @@
public static string Sanitize(string text) public static string Sanitize(string text)
{ {
foreach (string unsafeChar in SensitiveCharacters) foreach (string unsafeChar in SensitiveCharacters)
{
text = text.Replace(unsafeChar, $"\\{unsafeChar}"); text = text.Replace(unsafeChar, $"\\{unsafeChar}");
}
return text; return text;
} }
} }


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

@@ -26,14 +26,12 @@ namespace Discord


Task<IGuild> GetGuildAsync(ulong id); Task<IGuild> GetGuildAsync(ulong id);
Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(); Task<IReadOnlyCollection<IGuild>> GetGuildsAsync();
Task<IReadOnlyCollection<IUserGuild>> GetGuildSummariesAsync();
Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null);
Task<IInvite> GetInviteAsync(string inviteId); Task<IInvite> GetInviteAsync(string inviteId);


Task<IUser> GetUserAsync(ulong id); Task<IUser> GetUserAsync(ulong id);
Task<IUser> GetUserAsync(string username, string discriminator); Task<IUser> GetUserAsync(string username, string discriminator);
Task<IReadOnlyCollection<IUser>> QueryUsersAsync(string query, int limit);


Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(); Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync();
Task<IVoiceRegion> GetVoiceRegionAsync(string id); Task<IVoiceRegion> GetVoiceRegionAsync(string id);


src/Discord.Net.Utils/Logging/LogManager.cs → src/Discord.Net.Core/Logging/LogManager.cs View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Logging namespace Discord.Logging
@@ -6,6 +7,7 @@ namespace Discord.Logging
internal class LogManager internal class LogManager
{ {
public LogSeverity Level { get; } public LogSeverity Level { get; }
public Logger ClientLogger { get; }


public event Func<LogMessage, Task> Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } public event Func<LogMessage, Task> Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } }
private readonly AsyncEvent<Func<LogMessage, Task>> _messageEvent = new AsyncEvent<Func<LogMessage, Task>>(); private readonly AsyncEvent<Func<LogMessage, Task>> _messageEvent = new AsyncEvent<Func<LogMessage, Task>>();
@@ -13,6 +15,7 @@ namespace Discord.Logging
public LogManager(LogSeverity minSeverity) public LogManager(LogSeverity minSeverity)
{ {
Level = minSeverity; Level = minSeverity;
ClientLogger = new Logger(this, "Discord");
} }


public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null)
@@ -67,5 +70,21 @@ namespace Discord.Logging
=> LogAsync(LogSeverity.Debug, source, ex); => LogAsync(LogSeverity.Debug, source, ex);


public Logger CreateLogger(string name) => new Logger(this, name); public Logger CreateLogger(string name) => new Logger(this, name);

public async Task WriteInitialLog()
{
await ClientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false);
await ClientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false);
await ClientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false);
}
private static string ToArchString(Architecture arch)
{
switch (arch)
{
case Architecture.X64: return "x64";
case Architecture.X86: return "x86";
default: return arch.ToString();
}
}
} }
} }

src/Discord.Net.Utils/Logging/Logger.cs → src/Discord.Net.Core/Logging/Logger.cs View File


+ 10
- 0
src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs View File

@@ -19,6 +19,10 @@ namespace Discord.Net.Converters
return UserStatus.Online; return UserStatus.Online;
case "idle": case "idle":
return UserStatus.Idle; return UserStatus.Idle;
case "dnd":
return UserStatus.DoNotDisturb;
case "invisible":
return UserStatus.Invisible; //Should never happen
case "offline": case "offline":
return UserStatus.Offline; return UserStatus.Offline;
default: default:
@@ -36,6 +40,12 @@ namespace Discord.Net.Converters
case UserStatus.Idle: case UserStatus.Idle:
writer.WriteValue("idle"); writer.WriteValue("idle");
break; break;
case UserStatus.DoNotDisturb:
writer.WriteValue("dnd");
break;
case UserStatus.Invisible:
writer.WriteValue("invisible");
break;
case UserStatus.Offline: case UserStatus.Offline:
writer.WriteValue("offline"); writer.WriteValue("offline");
break; break;


src/Discord.Net.Utils/AsyncEvent.cs → src/Discord.Net.Core/Utils/AsyncEvent.cs View File


src/Discord.Net.Utils/ConcurrentHashSet.cs → src/Discord.Net.Core/Utils/ConcurrentHashSet.cs View File


src/Discord.Net.Utils/DateTimeUtils.cs → src/Discord.Net.Core/Utils/DateTimeUtils.cs View File


src/Discord.Net.Utils/Extensions/CollectionExtensions.cs → src/Discord.Net.Core/Utils/Extensions/CollectionExtensions.cs View File


src/Discord.Net.Utils/Extensions/Permissions.cs → src/Discord.Net.Core/Utils/Extensions/Permissions.cs View File


src/Discord.Net.Utils/Extensions/TaskCompletionSourceExtensions.cs → src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs View File


src/Discord.Net.Utils/MentionsHelper.cs → src/Discord.Net.Core/Utils/MentionsHelper.cs View File


src/Discord.Net.Utils/Paging/Page.cs → src/Discord.Net.Core/Utils/Paging/Page.cs View File


src/Discord.Net.Utils/Paging/PageInfo.cs → src/Discord.Net.Core/Utils/Paging/PageInfo.cs View File


src/Discord.Net.Utils/Paging/PagedEnumerator.cs → src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs View File


src/Discord.Net.Utils/Permissions.cs → src/Discord.Net.Core/Utils/Permissions.cs View File


src/Discord.Net.Utils/Preconditions.cs → src/Discord.Net.Core/Utils/Preconditions.cs View File


+ 6
- 0
src/Discord.Net.Rest/AssemblyInfo.cs View File

@@ -0,0 +1,6 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Discord.Net.Rpc")]
[assembly: InternalsVisibleTo("Discord.Net.WebSocket")]
[assembly: InternalsVisibleTo("Discord.Net.Commands")]
[assembly: InternalsVisibleTo("Discord.Net.Test")]

+ 118
- 0
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -0,0 +1,118 @@
using Discord.API.Rest;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.Rest
{
internal static class ClientHelper
{
//Applications
public static async Task<RestApplication> GetApplicationInfoAsync(DiscordClient client)
{
var model = await client.ApiClient.GetMyApplicationAsync().ConfigureAwait(false);
return RestApplication.Create(client, model);
}

public static async Task<RestChannel> GetChannelAsync(DiscordClient client,
ulong id)
{
var model = await client.ApiClient.GetChannelAsync(id).ConfigureAwait(false);
if (model != null)
return RestChannel.Create(client, model);
return null;
}
public static async Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync(DiscordClient client)
{
var models = await client.ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false);
return models.Select(x => RestDMChannel.Create(client, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(DiscordClient client)
{
var models = await client.ApiClient.GetMyConnectionsAsync().ConfigureAwait(false);
return models.Select(x => RestConnection.Create(x)).ToImmutableArray();
}
public static async Task<RestInvite> GetInviteAsync(DiscordClient client,
string inviteId)
{
var model = await client.ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false);
if (model != null)
return RestInvite.Create(client, model);
return null;
}
public static async Task<RestGuild> GetGuildAsync(DiscordClient client,
ulong id)
{
var model = await client.ApiClient.GetGuildAsync(id).ConfigureAwait(false);
if (model != null)
return RestGuild.Create(client, model);
return null;
}
public static async Task<RestGuildEmbed?> GetGuildEmbedAsync(DiscordClient client,
ulong id)
{
var model = await client.ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false);
if (model != null)
return RestGuildEmbed.Create(model);
return null;
}
public static async Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(DiscordClient client)
{
var models = await client.ApiClient.GetMyGuildsAsync().ConfigureAwait(false);
return models.Select(x => RestUserGuild.Create(client, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(DiscordClient client)
{
var summaryModels = await client.ApiClient.GetMyGuildsAsync().ConfigureAwait(false);
var guilds = ImmutableArray.CreateBuilder<RestGuild>(summaryModels.Count);
foreach (var summaryModel in summaryModels)
{
var guildModel = await client.ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false);
if (guildModel != null)
guilds.Add(RestGuild.Create(client, guildModel));
}
return guilds.ToImmutable();
}
public static async Task<RestGuild> CreateGuildAsync(DiscordClient client,
string name, IVoiceRegion region, Stream jpegIcon = null)
{
var args = new CreateGuildParams(name, region.Id);
var model = await client.ApiClient.CreateGuildAsync(args).ConfigureAwait(false);
return RestGuild.Create(client, model);
}
public static async Task<RestUser> GetUserAsync(DiscordClient client,
ulong id)
{
var model = await client.ApiClient.GetUserAsync(id).ConfigureAwait(false);
if (model != null)
return RestUser.Create(client, model);
return null;
}
public static async Task<RestUser> GetUserAsync(DiscordClient client,
string username, string discriminator)
{
var model = await client.ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false);
if (model != null)
return RestUser.Create(client, model);
return null;
}

public static async Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(DiscordClient client)
{
var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false);
return models.Select(x => RestVoiceRegion.Create(client, x)).ToImmutableArray();
}
public static async Task<RestVoiceRegion> GetVoiceRegionAsync(DiscordClient client,
string id)
{
var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false);
return models.Select(x => RestVoiceRegion.Create(client, x)).Where(x => x.Id == id).FirstOrDefault();
}
}
}

+ 162
- 0
src/Discord.Net.Rest/DiscordClient.cs View File

@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Discord.Logging;
using System.Collections.Immutable;

namespace Discord.Rest
{
public abstract class DiscordClient : IDiscordClient
{
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
private readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();

public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } }
private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>();
public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } }
private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>();

internal readonly Logger _restLogger, _queueLogger;
internal readonly SemaphoreSlim _connectionLock;
private bool _isFirstLogin;
private bool _isDisposed;

public API.DiscordRestApiClient ApiClient { get; }
internal LogManager LogManager { get; }
public LoginState LoginState { get; private set; }
public ISelfUser CurrentUser { get; protected set; }
/// <summary> Creates a new REST-only discord client. </summary>
internal DiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client)
{
ApiClient = client;
LogManager = new LogManager(config.LogLevel);
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);

_connectionLock = new SemaphoreSlim(1, 1);
_restLogger = LogManager.CreateLogger("Rest");
_queueLogger = LogManager.CreateLogger("Queue");
_isFirstLogin = true;

ApiClient.RequestQueue.RateLimitTriggered += async (id, bucket, millis) =>
{
await _queueLogger.WarningAsync($"Rate limit triggered (id = \"{id ?? "null"}\")").ConfigureAwait(false);
if (bucket == null && id != null)
await _queueLogger.WarningAsync($"Unknown rate limit bucket \"{id ?? "null"}\"").ConfigureAwait(false);
};
ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
}

/// <inheritdoc />
public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true)
{
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await LoginInternalAsync(tokenType, token).ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
}
private async Task LoginInternalAsync(TokenType tokenType, string token)
{
if (_isFirstLogin)
{
_isFirstLogin = false;
await LogManager.WriteInitialLog().ConfigureAwait(false);
}

if (LoginState != LoginState.LoggedOut)
await LogoutInternalAsync().ConfigureAwait(false);
LoginState = LoginState.LoggingIn;

try
{
await OnLoginAsync(tokenType, token).ConfigureAwait(false);
LoginState = LoginState.LoggedIn;
}
catch (Exception)
{
await LogoutInternalAsync().ConfigureAwait(false);
throw;
}

await _loggedInEvent.InvokeAsync().ConfigureAwait(false);
}
protected virtual Task OnLoginAsync(TokenType tokenType, string token) { return Task.CompletedTask; }

/// <inheritdoc />
public async Task LogoutAsync()
{
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await LogoutInternalAsync().ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
}
private async Task LogoutInternalAsync()
{
if (LoginState == LoginState.LoggedOut) return;
LoginState = LoginState.LoggingOut;

await ApiClient.LogoutAsync().ConfigureAwait(false);

await OnLogoutAsync().ConfigureAwait(false);
CurrentUser = null;
LoginState = LoginState.LoggedOut;

await _loggedOutEvent.InvokeAsync().ConfigureAwait(false);
}
protected virtual Task OnLogoutAsync() { return Task.CompletedTask; }

internal virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
ApiClient.Dispose();
_isDisposed = true;
}
}
/// <inheritdoc />
public void Dispose() => Dispose(true);

//IDiscordClient
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
ISelfUser IDiscordClient.CurrentUser => CurrentUser;

Task<IApplication> IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); }

Task<IChannel> IDiscordClient.GetChannelAsync(ulong id)
=> Task.FromResult<IChannel>(null);
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync()
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(ImmutableArray.Create<IPrivateChannel>());

Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync()
=> Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>());

Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId)
=> Task.FromResult<IInvite>(null);

Task<IGuild> IDiscordClient.GetGuildAsync(ulong id)
=> Task.FromResult<IGuild>(null);
Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync()
=> Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>());
Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) { throw new NotSupportedException(); }

Task<IUser> IDiscordClient.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(null);
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator)
=> Task.FromResult<IUser>(null);

Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync()
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(ImmutableArray.Create<IVoiceRegion>());
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id)
=> Task.FromResult<IVoiceRegion>(null);

Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); }
Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); }

}
}

+ 47
- 266
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -1,324 +1,105 @@
using Discord.API.Rest;
using Discord.Net;
using Discord.Net.Queue;
using System;
using Discord.Net.Queue;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Discord.Logging;


namespace Discord.Rest namespace Discord.Rest
{ {
public class DiscordRestClient : IDiscordClient
public class DiscordRestClient : DiscordClient, IDiscordClient
{ {
private readonly object _eventLock = new object();
public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser;


public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
private readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();

public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } }
private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>();
public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } }
private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>();

internal readonly Logger _clientLogger, _restLogger, _queueLogger;
internal readonly SemaphoreSlim _connectionLock;
private bool _isFirstLogSub;
internal bool _isDisposed;

public API.DiscordRestApiClient ApiClient { get; }
internal LogManager LogManager { get; }
public LoginState LoginState { get; private set; }
public RestSelfUser CurrentUser { get; private set; }

/// <summary> Creates a new REST-only discord client. </summary>
public DiscordRestClient() : this(new DiscordRestConfig()) { } public DiscordRestClient() : this(new DiscordRestConfig()) { }
public DiscordRestClient(DiscordRestConfig config) : this(config, CreateApiClient(config)) { }
/// <summary> Creates a new REST-only discord client. </summary>
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient client)
{
ApiClient = client;
LogManager = new LogManager(config.LogLevel);
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
_clientLogger = LogManager.CreateLogger("Client");
_restLogger = LogManager.CreateLogger("Rest");
_queueLogger = LogManager.CreateLogger("Queue");
_isFirstLogSub = true;
public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { }


_connectionLock = new SemaphoreSlim(1, 1);

ApiClient.RequestQueue.RateLimitTriggered += async (id, bucket, millis) =>
{
await _queueLogger.WarningAsync($"Rate limit triggered (id = \"{id ?? "null"}\")").ConfigureAwait(false);
if (bucket == null && id != null)
await _queueLogger.WarningAsync($"Unknown rate limit bucket \"{id ?? "null"}\"").ConfigureAwait(false);
};
ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
}
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue()); => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue());


/// <inheritdoc />
public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true)
{
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await LoginInternalAsync(tokenType, token).ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
}
private async Task LoginInternalAsync(TokenType tokenType, string token)
{
if (_isFirstLogSub)
{
_isFirstLogSub = false;
await WriteInitialLog().ConfigureAwait(false);
}

if (LoginState != LoginState.LoggedOut)
await LogoutInternalAsync().ConfigureAwait(false);
LoginState = LoginState.LoggingIn;

try
{
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false);
CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser);

await OnLoginAsync(tokenType, token).ConfigureAwait(false);
LoginState = LoginState.LoggedIn;
}
catch (Exception)
{
await LogoutInternalAsync().ConfigureAwait(false);
throw;
}

await _loggedInEvent.InvokeAsync().ConfigureAwait(false);
}
protected virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.CompletedTask;

/// <inheritdoc />
public async Task LogoutAsync()
protected override async Task OnLoginAsync(TokenType tokenType, string token)
{ {
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await LogoutInternalAsync().ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false);
base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser);
} }
private async Task LogoutInternalAsync()
{
if (LoginState == LoginState.LoggedOut) return;
LoginState = LoginState.LoggingOut;

await ApiClient.LogoutAsync().ConfigureAwait(false);
await OnLogoutAsync().ConfigureAwait(false);

CurrentUser = null;
LoginState = LoginState.LoggedOut;

await _loggedOutEvent.InvokeAsync().ConfigureAwait(false);
}
protected virtual Task OnLogoutAsync() => Task.CompletedTask;


/// <inheritdoc /> /// <inheritdoc />
public async Task<IApplication> GetApplicationInfoAsync()
{
var model = await ApiClient.GetMyApplicationAsync().ConfigureAwait(false);
return RestApplication.Create(this, model);
}
public Task<RestApplication> GetApplicationInfoAsync()
=> ClientHelper.GetApplicationInfoAsync(this);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<IChannel> GetChannelAsync(ulong id)
{
var model = await ApiClient.GetChannelAsync(id).ConfigureAwait(false);
if (model != null)
{
switch (model.Type)
{
case ChannelType.Text:
return RestTextChannel.Create(this, model);
case ChannelType.Voice:
return RestVoiceChannel.Create(this, model);
case ChannelType.DM:
return RestDMChannel.Create(this, model);
case ChannelType.Group:
return RestGroupChannel.Create(this, model);
default:
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
}
}
return null;
}
public Task<RestChannel> GetChannelAsync(ulong id)
=> ClientHelper.GetChannelAsync(this, id);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync()
{
var models = await ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false);
return models.Select(x => RestDMChannel.Create(this, x)).ToImmutableArray();
}
public Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync()
=> ClientHelper.GetPrivateChannelsAsync(this);

/// <inheritdoc /> /// <inheritdoc />
public async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
{
var models = await ApiClient.GetMyConnectionsAsync().ConfigureAwait(false);
return models.Select(x => RestConnection.Create(x)).ToImmutableArray();
}
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
=> ClientHelper.GetConnectionsAsync(this);


/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<RestInvite> GetInviteAsync(string inviteId)
{
var model = await ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false);
if (model != null)
return RestInvite.Create(this, model);
return null;
}
public Task<RestInvite> GetInviteAsync(string inviteId)
=> ClientHelper.GetInviteAsync(this, inviteId);


/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<RestGuild> GetGuildAsync(ulong id)
{
var model = await ApiClient.GetGuildAsync(id).ConfigureAwait(false);
if (model != null)
return RestGuild.Create(this, model);
return null;
}
public Task<RestGuild> GetGuildAsync(ulong id)
=> ClientHelper.GetGuildAsync(this, id);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id)
{
var model = await ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false);
if (model != null)
return RestGuildEmbed.Create(model);
return null;
}
public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id)
=> ClientHelper.GetGuildEmbedAsync(this, id);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync()
{
var models = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false);
return models.Select(x => RestUserGuild.Create(this, x)).ToImmutableArray();
}
public Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync()
=> ClientHelper.GetGuildSummariesAsync(this);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync()
{
var summaryModels = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false);
var guilds = ImmutableArray.CreateBuilder<RestGuild>(summaryModels.Count);
foreach (var summaryModel in summaryModels)
{
var guildModel = await ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false);
if (guildModel != null)
guilds.Add(RestGuild.Create(this, guildModel));
}
return guilds.ToImmutable();
}
public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync()
=> ClientHelper.GetGuildsAsync(this);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
{
var args = new CreateGuildParams(name, region.Id);
var model = await ApiClient.CreateGuildAsync(args).ConfigureAwait(false);
return RestGuild.Create(this, model);
}
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon);


/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<RestUser> GetUserAsync(ulong id)
{
var model = await ApiClient.GetUserAsync(id).ConfigureAwait(false);
if (model != null)
return RestUser.Create(this, model);
return null;
}
public Task<RestUser> GetUserAsync(ulong id)
=> ClientHelper.GetUserAsync(this, id);
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<RestUser> GetUserAsync(string username, string discriminator)
{
var model = await ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false);
if (model != null)
return RestUser.Create(this, model);
return null;
}
public Task<RestUser> GetUserAsync(string username, string discriminator)
=> ClientHelper.GetUserAsync(this, username, discriminator);


/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<IReadOnlyCollection<RestUser>> QueryUsersAsync(string query, int limit)
{
var models = await ApiClient.QueryUsersAsync(query, limit).ConfigureAwait(false);
return models.Select(x => RestUser.Create(this, x)).ToImmutableArray();
}

/// <inheritdoc />
public virtual async Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync()
{
var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false);
return models.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableArray();
}
/// <inheritdoc />
public virtual async Task<RestVoiceRegion> GetVoiceRegionAsync(string id)
{
var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false);
return models.Select(x => RestVoiceRegion.Create(this, x)).Where(x => x.Id == id).FirstOrDefault();
}

internal virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
ApiClient.Dispose();
_isDisposed = true;
}
}
public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync()
=> ClientHelper.GetVoiceRegionsAsync(this);
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => Dispose(true);

private async Task WriteInitialLog()
{
/*if (this is DiscordSocketClient)
await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, {DiscordSocketConfig.GatewayEncoding})").ConfigureAwait(false);
else if (this is DiscordRpcClient)
await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, RPC API v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false);*/
await _clientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false);
await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false);
await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false);
await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false);
}
private static string ToArchString(Architecture arch)
{
switch (arch)
{
case Architecture.X64: return "x64";
case Architecture.X86: return "x86";
default: return arch.ToString();
}
}
public Task<RestVoiceRegion> GetVoiceRegionAsync(string id)
=> ClientHelper.GetVoiceRegionAsync(this, id);


//IDiscordClient //IDiscordClient
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
ISelfUser IDiscordClient.CurrentUser => CurrentUser;
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync()
=> await GetApplicationInfoAsync().ConfigureAwait(false);

async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id)
=> await GetChannelAsync(id);
async Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync()
=> await GetPrivateChannelsAsync();


async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync() async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync()
=> await GetConnectionsAsync().ConfigureAwait(false); => await GetConnectionsAsync().ConfigureAwait(false);

async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId) async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId)
=> await GetInviteAsync(inviteId).ConfigureAwait(false); => await GetInviteAsync(inviteId).ConfigureAwait(false);

async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id) async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id)
=> await GetGuildAsync(id).ConfigureAwait(false); => await GetGuildAsync(id).ConfigureAwait(false);
async Task<IReadOnlyCollection<IUserGuild>> IDiscordClient.GetGuildSummariesAsync()
=> await GetGuildSummariesAsync().ConfigureAwait(false);
async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync() async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync()
=> await GetGuildsAsync().ConfigureAwait(false); => await GetGuildsAsync().ConfigureAwait(false);
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon)
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false);

async Task<IUser> IDiscordClient.GetUserAsync(ulong id) async Task<IUser> IDiscordClient.GetUserAsync(ulong id)
=> await GetUserAsync(id).ConfigureAwait(false); => await GetUserAsync(id).ConfigureAwait(false);
async Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator) async Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator)
=> await GetUserAsync(username, discriminator).ConfigureAwait(false); => await GetUserAsync(username, discriminator).ConfigureAwait(false);
async Task<IReadOnlyCollection<IUser>> IDiscordClient.QueryUsersAsync(string query, int limit)
=> await QueryUsersAsync(query, limit).ConfigureAwait(false);

async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync() async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync()
=> await GetVoiceRegionsAsync().ConfigureAwait(false); => await GetVoiceRegionsAsync().ConfigureAwait(false);
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id) async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id)
=> await GetVoiceRegionAsync(id).ConfigureAwait(false); => await GetVoiceRegionAsync(id).ConfigureAwait(false);

Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); }
Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); }
} }
} }

+ 22
- 22
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -12,33 +12,33 @@ namespace Discord.Rest
internal static class ChannelHelper internal static class ChannelHelper
{ {
//General //General
public static async Task<Model> GetAsync(IGuildChannel channel, DiscordRestClient client)
public static async Task<Model> GetAsync(IGuildChannel channel, DiscordClient client)
{ {
return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false);
} }
public static async Task<Model> GetAsync(IPrivateChannel channel, DiscordRestClient client)
public static async Task<Model> GetAsync(IPrivateChannel channel, DiscordClient client)
{ {
return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false);
} }
public static async Task DeleteAsync(IChannel channel, DiscordRestClient client)
public static async Task DeleteAsync(IChannel channel, DiscordClient client)
{ {
await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false);
} }
public static async Task ModifyAsync(IGuildChannel channel, DiscordRestClient client,
public static async Task ModifyAsync(IGuildChannel channel, DiscordClient client,
Action<ModifyGuildChannelParams> func) Action<ModifyGuildChannelParams> func)
{ {
var args = new ModifyGuildChannelParams(); var args = new ModifyGuildChannelParams();
func(args); func(args);
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args);
} }
public static async Task ModifyAsync(ITextChannel channel, DiscordRestClient client,
public static async Task ModifyAsync(ITextChannel channel, DiscordClient client,
Action<ModifyTextChannelParams> func) Action<ModifyTextChannelParams> func)
{ {
var args = new ModifyTextChannelParams(); var args = new ModifyTextChannelParams();
func(args); func(args);
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args);
} }
public static async Task ModifyAsync(IVoiceChannel channel, DiscordRestClient client,
public static async Task ModifyAsync(IVoiceChannel channel, DiscordClient client,
Action<ModifyVoiceChannelParams> func) Action<ModifyVoiceChannelParams> func)
{ {
var args = new ModifyVoiceChannelParams(); var args = new ModifyVoiceChannelParams();
@@ -47,12 +47,12 @@ namespace Discord.Rest
} }


//Invites //Invites
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IChannel channel, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IChannel channel, DiscordClient client)
{ {
var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id);
return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray();
} }
public static async Task<RestInviteMetadata> CreateInviteAsync(IChannel channel, DiscordRestClient client,
public static async Task<RestInviteMetadata> CreateInviteAsync(IChannel channel, DiscordClient client,
int? maxAge, int? maxUses, bool isTemporary) int? maxAge, int? maxUses, bool isTemporary)
{ {
var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; var args = new CreateChannelInviteParams { IsTemporary = isTemporary };
@@ -65,13 +65,13 @@ namespace Discord.Rest
} }


//Messages //Messages
public static async Task<RestMessage> GetMessageAsync(IChannel channel, DiscordRestClient client,
public static async Task<RestMessage> GetMessageAsync(IChannel channel, DiscordClient client,
ulong id) ulong id)
{ {
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false);
return RestMessage.Create(client, model); return RestMessage.Create(client, model);
} }
public static PagedAsyncEnumerable<RestMessage> GetMessagesAsync(IChannel channel, DiscordRestClient client,
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IChannel channel, DiscordClient client,
ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch)
{ {
//TODO: Test this with Around direction //TODO: Test this with Around direction
@@ -102,13 +102,13 @@ namespace Discord.Rest
count: (uint)limit count: (uint)limit
); );
} }
public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IChannel channel, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IChannel channel, DiscordClient client)
{ {
var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false);
return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray();
} }


public static async Task<RestUserMessage> SendMessageAsync(IChannel channel, DiscordRestClient client,
public static async Task<RestUserMessage> SendMessageAsync(IChannel channel, DiscordClient client,
string text, bool isTTS) string text, bool isTTS)
{ {
var args = new CreateMessageParams(text) { IsTTS = isTTS }; var args = new CreateMessageParams(text) { IsTTS = isTTS };
@@ -116,14 +116,14 @@ namespace Discord.Rest
return RestUserMessage.Create(client, model); return RestUserMessage.Create(client, model);
} }


public static Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordRestClient client,
public static Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordClient client,
string filePath, string text, bool isTTS) string filePath, string text, bool isTTS)
{ {
string filename = Path.GetFileName(filePath); string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath)) using (var file = File.OpenRead(filePath))
return SendFileAsync(channel, client, file, filename, text, isTTS); return SendFileAsync(channel, client, file, filename, text, isTTS);
} }
public static async Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordRestClient client,
public static async Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordClient client,
Stream stream, string filename, string text, bool isTTS) Stream stream, string filename, string text, bool isTTS)
{ {
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
@@ -131,7 +131,7 @@ namespace Discord.Rest
return RestUserMessage.Create(client, model); return RestUserMessage.Create(client, model);
} }


public static async Task DeleteMessagesAsync(IChannel channel, DiscordRestClient client,
public static async Task DeleteMessagesAsync(IChannel channel, DiscordClient client,
IEnumerable<IMessage> messages) IEnumerable<IMessage> messages)
{ {
var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray());
@@ -139,31 +139,31 @@ namespace Discord.Rest
} }


//Permission Overwrites //Permission Overwrites
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client,
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client,
IUser user, OverwritePermissions perms) IUser user, OverwritePermissions perms)
{ {
var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue);
await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false);
} }
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client,
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client,
IRole role, OverwritePermissions perms) IRole role, OverwritePermissions perms)
{ {
var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue);
await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false);
} }
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client,
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client,
IUser user) IUser user)
{ {
await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false);
} }
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client,
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client,
IRole role) IRole role)
{ {
await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false);
} }


//Users //Users
public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, DiscordRestClient client,
public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, DiscordClient client,
ulong id) ulong id)
{ {
var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id);
@@ -175,7 +175,7 @@ namespace Discord.Rest


return user; return user;
} }
public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, DiscordRestClient client,
public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, DiscordClient client,
ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch)
{ {
return new PagedAsyncEnumerable<RestGuildUser>( return new PagedAsyncEnumerable<RestGuildUser>(
@@ -203,7 +203,7 @@ namespace Discord.Rest
} }


//Typing //Typing
public static IDisposable EnterTypingState(IChannel channel, DiscordRestClient client)
public static IDisposable EnterTypingState(IChannel channel, DiscordClient client)
{ {
throw new NotImplementedException(); //TODO: Impl throw new NotImplementedException(); //TODO: Impl
} }


+ 48
- 0
src/Discord.Net.Rest/Entities/Channels/RestChannel.cs View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;

namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public abstract class RestChannel : RestEntity<ulong>, IChannel, IUpdateable
{
internal RestChannel(DiscordClient discord, ulong id)
: base(discord, id)
{
}
internal static RestChannel Create(DiscordClient discord, Model model)
{
switch (model.Type)
{
case ChannelType.Text:
return RestTextChannel.Create(discord, model);
case ChannelType.Voice:
return RestVoiceChannel.Create(discord, model);
case ChannelType.DM:
return RestDMChannel.Create(discord, model);
case ChannelType.Group:
return RestGroupChannel.Create(discord, model);
default:
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
}
}
internal abstract void Update(Model model);

public abstract Task UpdateAsync();

//IChannel
IReadOnlyCollection<IUser> IChannel.CachedUsers => ImmutableArray.Create<IUser>();

IUser IChannel.GetCachedUser(ulong id)
=> null;
Task<IUser> IChannel.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(null);
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable();
}
}

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

@@ -10,28 +10,31 @@ using Model = Discord.API.Channel;
namespace Discord.Rest namespace Discord.Rest
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestDMChannel : RestEntity<ulong>, IDMChannel, IUpdateable
public class RestDMChannel : RestChannel, IDMChannel, IUpdateable
{ {
public RestUser Recipient { get; }
public RestUser CurrentUser { get; private set; }
public RestUser Recipient { get; private set; }


public IReadOnlyCollection<RestUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient);
public IReadOnlyCollection<RestUser> Users => ImmutableArray.Create(CurrentUser, Recipient);


internal RestDMChannel(DiscordRestClient discord, ulong id)
internal RestDMChannel(DiscordClient discord, ulong id, ulong recipientId)
: base(discord, id) : base(discord, id)
{ {
Recipient = new RestUser(Discord, recipientId);
CurrentUser = new RestUser(Discord, discord.CurrentUser.Id);
} }
internal static RestDMChannel Create(DiscordRestClient discord, Model model)
internal new static RestDMChannel Create(DiscordClient discord, Model model)
{ {
var entity = new RestDMChannel(discord, model.Id);
var entity = new RestDMChannel(discord, model.Id, model.Recipients.Value[0].Id);
entity.Update(model); entity.Update(model);
return entity; return entity;
} }
internal void Update(Model model)
internal override void Update(Model model)
{ {
Recipient.Update(model.Recipients.Value[0]); Recipient.Update(model.Recipients.Value[0]);
} }


public async Task UpdateAsync()
public override async Task UpdateAsync()
=> Update(await ChannelHelper.GetAsync(this, Discord)); => Update(await ChannelHelper.GetAsync(this, Discord));
public Task CloseAsync() public Task CloseAsync()
=> ChannelHelper.DeleteAsync(this, Discord); => ChannelHelper.DeleteAsync(this, Discord);
@@ -41,7 +44,7 @@ namespace Discord.Rest
if (id == Recipient.Id) if (id == Recipient.Id)
return Recipient; return Recipient;
else if (id == Discord.CurrentUser.Id) else if (id == Discord.CurrentUser.Id)
return Discord.CurrentUser;
return CurrentUser;
else else
return null; return null;
} }


+ 5
- 5
src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs View File

@@ -10,7 +10,7 @@ using Model = Discord.API.Channel;
namespace Discord.Rest namespace Discord.Rest
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestGroupChannel : RestEntity<ulong>, IGroupChannel, IUpdateable
public class RestGroupChannel : RestChannel, IGroupChannel, IUpdateable
{ {
private string _iconId; private string _iconId;
private ImmutableDictionary<ulong, RestGroupUser> _users; private ImmutableDictionary<ulong, RestGroupUser> _users;
@@ -21,17 +21,17 @@ namespace Discord.Rest
public IReadOnlyCollection<RestGroupUser> Recipients public IReadOnlyCollection<RestGroupUser> Recipients
=> _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1);


internal RestGroupChannel(DiscordRestClient discord, ulong id)
internal RestGroupChannel(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestGroupChannel Create(DiscordRestClient discord, Model model)
internal new static RestGroupChannel Create(DiscordClient discord, Model model)
{ {
var entity = new RestGroupChannel(discord, model.Id); var entity = new RestGroupChannel(discord, model.Id);
entity.Update(model); entity.Update(model);
return entity; return entity;
} }
internal void Update(Model model)
internal override void Update(Model model)
{ {
if (model.Name.IsSpecified) if (model.Name.IsSpecified)
Name = model.Name.Value; Name = model.Name.Value;
@@ -49,7 +49,7 @@ namespace Discord.Rest
_users = users.ToImmutable(); _users = users.ToImmutable();
} }


public async Task UpdateAsync()
public override async Task UpdateAsync()
=> Update(await ChannelHelper.GetAsync(this, Discord)); => Update(await ChannelHelper.GetAsync(this, Discord));
public Task LeaveAsync() public Task LeaveAsync()
=> ChannelHelper.DeleteAsync(this, Discord); => ChannelHelper.DeleteAsync(this, Discord);


+ 5
- 5
src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs View File

@@ -10,7 +10,7 @@ using Model = Discord.API.Channel;
namespace Discord.Rest namespace Discord.Rest
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public abstract class RestGuildChannel : RestEntity<ulong>, IGuildChannel, IUpdateable
public abstract class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable
{ {
private ImmutableArray<Overwrite> _overwrites; private ImmutableArray<Overwrite> _overwrites;


@@ -21,12 +21,12 @@ namespace Discord.Rest
public string Name { get; private set; } public string Name { get; private set; }
public int Position { get; private set; } public int Position { get; private set; }


internal RestGuildChannel(DiscordRestClient discord, ulong id, ulong guildId)
internal RestGuildChannel(DiscordClient discord, ulong id, ulong guildId)
: base(discord, id) : base(discord, id)
{ {
GuildId = guildId; GuildId = guildId;
} }
internal static RestGuildChannel Create(DiscordRestClient discord, Model model)
internal new static RestGuildChannel Create(DiscordClient discord, Model model)
{ {
switch (model.Type) switch (model.Type)
{ {
@@ -38,7 +38,7 @@ namespace Discord.Rest
throw new InvalidOperationException("Unknown guild channel type"); throw new InvalidOperationException("Unknown guild channel type");
} }
} }
internal virtual void Update(Model model)
internal override void Update(Model model)
{ {
Name = model.Name.Value; Name = model.Name.Value;
Position = model.Position.Value; Position = model.Position.Value;
@@ -50,7 +50,7 @@ namespace Discord.Rest
_overwrites = newOverwrites.ToImmutable(); _overwrites = newOverwrites.ToImmutable();
} }


public async Task UpdateAsync()
public override async Task UpdateAsync()
=> Update(await ChannelHelper.GetAsync(this, Discord)); => Update(await ChannelHelper.GetAsync(this, Discord));
public Task ModifyAsync(Action<ModifyGuildChannelParams> func) public Task ModifyAsync(Action<ModifyGuildChannelParams> func)
=> ChannelHelper.ModifyAsync(this, Discord, func); => ChannelHelper.ModifyAsync(this, Discord, func);


+ 2
- 2
src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs View File

@@ -16,11 +16,11 @@ namespace Discord.Rest


public string Mention => MentionUtils.MentionChannel(Id); public string Mention => MentionUtils.MentionChannel(Id);


internal RestTextChannel(DiscordRestClient discord, ulong id, ulong guildId)
internal RestTextChannel(DiscordClient discord, ulong id, ulong guildId)
: base(discord, id, guildId) : base(discord, id, guildId)
{ {
} }
internal new static RestTextChannel Create(DiscordRestClient discord, Model model)
internal new static RestTextChannel Create(DiscordClient discord, Model model)
{ {
var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value);
entity.Update(model); entity.Update(model);


+ 2
- 2
src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs View File

@@ -16,11 +16,11 @@ namespace Discord.Rest
public int Bitrate { get; private set; } public int Bitrate { get; private set; }
public int UserLimit { get; private set; } public int UserLimit { get; private set; }


internal RestVoiceChannel(DiscordRestClient discord, ulong id, ulong guildId)
internal RestVoiceChannel(DiscordClient discord, ulong id, ulong guildId)
: base(discord, id, guildId) : base(discord, id, guildId)
{ {
} }
internal new static RestVoiceChannel Create(DiscordRestClient discord, Model model)
internal new static RestVoiceChannel Create(DiscordClient discord, Model model)
{ {
var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value);
entity.Update(model); entity.Update(model);


+ 21
- 21
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Rest
internal static class GuildHelper internal static class GuildHelper
{ {
//General //General
public static async Task<Model> ModifyAsync(IGuild guild, DiscordRestClient client,
public static async Task<Model> ModifyAsync(IGuild guild, DiscordClient client,
Action<ModifyGuildParams> func) Action<ModifyGuildParams> func)
{ {
if (func == null) throw new NullReferenceException(nameof(func)); if (func == null) throw new NullReferenceException(nameof(func));
@@ -28,7 +28,7 @@ namespace Discord.Rest


return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false);
} }
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, DiscordRestClient client,
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, DiscordClient client,
Action<ModifyGuildEmbedParams> func) Action<ModifyGuildEmbedParams> func)
{ {
if (func == null) throw new NullReferenceException(nameof(func)); if (func == null) throw new NullReferenceException(nameof(func));
@@ -37,46 +37,46 @@ namespace Discord.Rest
func(args); func(args);
return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false);
} }
public static async Task ModifyChannelsAsync(IGuild guild, DiscordRestClient client,
public static async Task ModifyChannelsAsync(IGuild guild, DiscordClient client,
IEnumerable<ModifyGuildChannelsParams> args) IEnumerable<ModifyGuildChannelsParams> args)
{ {
await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false);
} }
public static async Task<IReadOnlyCollection<RoleModel>> ModifyRolesAsync(IGuild guild, DiscordRestClient client,
public static async Task<IReadOnlyCollection<RoleModel>> ModifyRolesAsync(IGuild guild, DiscordClient client,
IEnumerable<ModifyGuildRolesParams> args) IEnumerable<ModifyGuildRolesParams> args)
{ {
return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false);
} }
public static async Task LeaveAsync(IGuild guild, DiscordRestClient client)
public static async Task LeaveAsync(IGuild guild, DiscordClient client)
{ {
await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false);
} }
public static async Task DeleteAsync(IGuild guild, DiscordRestClient client)
public static async Task DeleteAsync(IGuild guild, DiscordClient client)
{ {
await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false);
} }


//Bans //Bans
public static async Task<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, DiscordClient client)
{ {
var models = await client.ApiClient.GetGuildBansAsync(guild.Id); var models = await client.ApiClient.GetGuildBansAsync(guild.Id);
return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray();
} }
public static async Task AddBanAsync(IGuild guild, DiscordRestClient client,
public static async Task AddBanAsync(IGuild guild, DiscordClient client,
ulong userId, int pruneDays) ulong userId, int pruneDays)
{ {
var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays };
await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args);
} }
public static async Task RemoveBanAsync(IGuild guild, DiscordRestClient client,
public static async Task RemoveBanAsync(IGuild guild, DiscordClient client,
ulong userId) ulong userId)
{ {
await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId);
} }


//Channels //Channels
public static async Task<RestGuildChannel> GetChannelAsync(IGuild guild, DiscordRestClient client,
public static async Task<RestGuildChannel> GetChannelAsync(IGuild guild, DiscordClient client,
ulong id) ulong id)
{ {
var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false);
@@ -84,12 +84,12 @@ namespace Discord.Rest
return RestGuildChannel.Create(client, model); return RestGuildChannel.Create(client, model);
return null; return null;
} }
public static async Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync(IGuild guild, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync(IGuild guild, DiscordClient client)
{ {
var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false);
return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray();
} }
public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, DiscordRestClient client,
public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, DiscordClient client,
string name) string name)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));
@@ -98,7 +98,7 @@ namespace Discord.Rest
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false);
return RestTextChannel.Create(client, model); return RestTextChannel.Create(client, model);
} }
public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, DiscordRestClient client,
public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, DiscordClient client,
string name) string name)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));
@@ -109,12 +109,12 @@ namespace Discord.Rest
} }


//Integrations //Integrations
public static async Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(IGuild guild, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(IGuild guild, DiscordClient client)
{ {
var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false); var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false);
return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray();
} }
public static async Task<RestGuildIntegration> CreateIntegrationAsync(IGuild guild, DiscordRestClient client,
public static async Task<RestGuildIntegration> CreateIntegrationAsync(IGuild guild, DiscordClient client,
ulong id, string type) ulong id, string type)
{ {
var args = new CreateGuildIntegrationParams(id, type); var args = new CreateGuildIntegrationParams(id, type);
@@ -123,14 +123,14 @@ namespace Discord.Rest
} }


//Invites //Invites
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuild guild, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuild guild, DiscordClient client)
{ {
var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false); var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false);
return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray();
} }


//Roles //Roles
public static async Task<RestRole> CreateRoleAsync(IGuild guild, DiscordRestClient client,
public static async Task<RestRole> CreateRoleAsync(IGuild guild, DiscordClient client,
string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));
@@ -150,7 +150,7 @@ namespace Discord.Rest
} }


//Users //Users
public static async Task<RestGuildUser> GetUserAsync(IGuild guild, DiscordRestClient client,
public static async Task<RestGuildUser> GetUserAsync(IGuild guild, DiscordClient client,
ulong id) ulong id)
{ {
var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false);
@@ -158,17 +158,17 @@ namespace Discord.Rest
return RestGuildUser.Create(client, model); return RestGuildUser.Create(client, model);
return null; return null;
} }
public static async Task<RestGuildUser> GetCurrentUserAsync(IGuild guild, DiscordRestClient client)
public static async Task<RestGuildUser> GetCurrentUserAsync(IGuild guild, DiscordClient client)
{ {
return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false);
} }
public static async Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuild guild, DiscordRestClient client)
public static async Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuild guild, DiscordClient client)
{ {
var args = new GetGuildMembersParams(); var args = new GetGuildMembersParams();
var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false);
return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray();
} }
public static async Task<int> PruneUsersAsync(IGuild guild, DiscordRestClient client,
public static async Task<int> PruneUsersAsync(IGuild guild, DiscordClient client,
int days = 30, bool simulate = false) int days = 30, bool simulate = false)
{ {
var args = new GuildPruneParams(days); var args = new GuildPruneParams(days);


+ 1
- 1
src/Discord.Net.Rest/Entities/Guilds/RestBan.cs View File

@@ -14,7 +14,7 @@ namespace Discord.Rest
User = user; User = user;
Reason = reason; Reason = reason;
} }
internal static RestBan Create(DiscordRestClient client, Model model)
internal static RestBan Create(DiscordClient client, Model model)
{ {
return new RestBan(RestUser.Create(client, model.User), model.Reason); return new RestBan(RestUser.Create(client, model.User), model.Reason);
} }


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

@@ -39,11 +39,11 @@ namespace Discord.Rest
public IReadOnlyCollection<Emoji> Emojis => _emojis; public IReadOnlyCollection<Emoji> Emojis => _emojis;
public IReadOnlyCollection<string> Features => _features; public IReadOnlyCollection<string> Features => _features;


internal RestGuild(DiscordRestClient client, ulong id)
internal RestGuild(DiscordClient client, ulong id)
: base(client, id) : base(client, id)
{ {
} }
internal static RestGuild Create(DiscordRestClient discord, Model model)
internal static RestGuild Create(DiscordClient discord, Model model)
{ {
var entity = new RestGuild(discord, model.Id); var entity = new RestGuild(discord, model.Id);
entity.Update(model); entity.Update(model);


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

@@ -25,11 +25,11 @@ namespace Discord.Rest


public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks);


internal RestGuildIntegration(DiscordRestClient discord, ulong id)
internal RestGuildIntegration(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestGuildIntegration Create(DiscordRestClient discord, Model model)
internal static RestGuildIntegration Create(DiscordClient discord, Model model)
{ {
var entity = new RestGuildIntegration(discord, model.Id); var entity = new RestGuildIntegration(discord, model.Id);
entity.Update(model); entity.Update(model);


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

@@ -15,11 +15,11 @@ namespace Discord.Rest


public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId);


internal RestUserGuild(DiscordRestClient discord, ulong id)
internal RestUserGuild(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestUserGuild Create(DiscordRestClient discord, Model model)
internal static RestUserGuild Create(DiscordClient discord, Model model)
{ {
var entity = new RestUserGuild(discord, model.Id); var entity = new RestUserGuild(discord, model.Id);
entity.Update(model); entity.Update(model);


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

@@ -13,11 +13,11 @@ namespace Discord
public string SampleHostname { get; private set; } public string SampleHostname { get; private set; }
public int SamplePort { get; private set; } public int SamplePort { get; private set; }


internal RestVoiceRegion(DiscordRestClient client, string id)
internal RestVoiceRegion(DiscordClient client, string id)
: base(client, id) : base(client, id)
{ {
} }
internal static RestVoiceRegion Create(DiscordRestClient client, Model model)
internal static RestVoiceRegion Create(DiscordClient client, Model model)
{ {
var entity = new RestVoiceRegion(client, model.Id); var entity = new RestVoiceRegion(client, model.Id);
entity.Update(model); entity.Update(model);


+ 3
- 3
src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs View File

@@ -5,15 +5,15 @@ namespace Discord.Rest
{ {
internal static class InviteHelper internal static class InviteHelper
{ {
public static async Task<Model> GetAsync(IInvite invite, DiscordRestClient client)
public static async Task<Model> GetAsync(IInvite invite, DiscordClient client)
{ {
return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false);
} }
public static async Task AcceptAsync(IInvite invite, DiscordRestClient client)
public static async Task AcceptAsync(IInvite invite, DiscordClient client)
{ {
await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false);
} }
public static async Task DeleteAsync(IInvite invite, DiscordRestClient client)
public static async Task DeleteAsync(IInvite invite, DiscordClient client)
{ {
await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false);
} }


+ 2
- 2
src/Discord.Net.Rest/Entities/Invites/RestInvite.cs View File

@@ -16,11 +16,11 @@ namespace Discord.Rest
public string Code => Id; public string Code => Id;
public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; public string Url => $"{DiscordConfig.InviteUrl}/{Code}";


internal RestInvite(DiscordRestClient discord, string id)
internal RestInvite(DiscordClient discord, string id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestInvite Create(DiscordRestClient discord, Model model)
internal static RestInvite Create(DiscordClient discord, Model model)
{ {
var entity = new RestInvite(discord, model.Code); var entity = new RestInvite(discord, model.Code);
entity.Update(model); entity.Update(model);


+ 2
- 2
src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs View File

@@ -18,11 +18,11 @@ namespace Discord.Rest


public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks);


internal RestInviteMetadata(DiscordRestClient discord, string id)
internal RestInviteMetadata(DiscordClient discord, string id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestInviteMetadata Create(DiscordRestClient discord, Model model)
internal static RestInviteMetadata Create(DiscordClient discord, Model model)
{ {
var entity = new RestInviteMetadata(discord, model.Code); var entity = new RestInviteMetadata(discord, model.Code);
entity.Update(model); entity.Update(model);


+ 5
- 5
src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs View File

@@ -6,26 +6,26 @@ namespace Discord.Rest
{ {
internal static class MessageHelper internal static class MessageHelper
{ {
public static async Task GetAsync(IMessage msg, DiscordRestClient client)
public static async Task GetAsync(IMessage msg, DiscordClient client)
{ {
await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id);
} }
public static async Task ModifyAsync(IMessage msg, DiscordRestClient client, Action<ModifyMessageParams> func)
public static async Task ModifyAsync(IMessage msg, DiscordClient client, Action<ModifyMessageParams> func)
{ {
var args = new ModifyMessageParams(); var args = new ModifyMessageParams();
func(args); func(args);
await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args);
} }
public static async Task DeleteAsync(IMessage msg, DiscordRestClient client)
public static async Task DeleteAsync(IMessage msg, DiscordClient client)
{ {
await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id);
} }


public static async Task PinAsync(IMessage msg, DiscordRestClient client)
public static async Task PinAsync(IMessage msg, DiscordClient client)
{ {
await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id);
} }
public static async Task UnpinAsync(IMessage msg, DiscordRestClient client)
public static async Task UnpinAsync(IMessage msg, DiscordClient client)
{ {
await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id);
} }


+ 1
- 0
src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs View File

@@ -2,6 +2,7 @@


namespace Discord namespace Discord
{ {
//TODO: Rename to Attachment?
public class RestAttachment : IAttachment public class RestAttachment : IAttachment
{ {
public ulong Id { get; } public ulong Id { get; }


+ 2
- 2
src/Discord.Net.Rest/Entities/Messages/RestMessage.cs View File

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


public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);


internal RestMessage(DiscordRestClient discord, ulong id, ulong channelId)
internal RestMessage(DiscordClient discord, ulong id, ulong channelId)
: base(discord, id) : base(discord, id)
{ {
ChannelId = channelId; ChannelId = channelId;
} }
internal static RestMessage Create(DiscordRestClient discord, Model model)
internal static RestMessage Create(DiscordClient discord, Model model)
{ {
if (model.Type == MessageType.Default) if (model.Type == MessageType.Default)
return RestUserMessage.Create(discord, model); return RestUserMessage.Create(discord, model);


+ 2
- 2
src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs View File

@@ -8,11 +8,11 @@ namespace Discord.Rest
{ {
public MessageType Type { get; private set; } public MessageType Type { get; private set; }


internal RestSystemMessage(DiscordRestClient discord, ulong id, ulong channelId)
internal RestSystemMessage(DiscordClient discord, ulong id, ulong channelId)
: base(discord, id, channelId) : base(discord, id, channelId)
{ {
} }
internal new static RestSystemMessage Create(DiscordRestClient discord, Model model)
internal new static RestSystemMessage Create(DiscordClient discord, Model model)
{ {
var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); var entity = new RestSystemMessage(discord, model.Id, model.ChannelId);
entity.Update(model); entity.Update(model);


+ 2
- 2
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

@@ -30,11 +30,11 @@ namespace Discord.Rest
public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles; public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles;
public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers; public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers;


internal RestUserMessage(DiscordRestClient discord, ulong id, ulong channelId)
internal RestUserMessage(DiscordClient discord, ulong id, ulong channelId)
: base(discord, id, channelId) : base(discord, id, channelId)
{ {
} }
internal new static RestUserMessage Create(DiscordRestClient discord, Model model)
internal new static RestUserMessage Create(DiscordClient discord, Model model)
{ {
var entity = new RestUserMessage(discord, model.Id, model.ChannelId); var entity = new RestUserMessage(discord, model.Id, model.ChannelId);
entity.Update(model); entity.Update(model);


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

@@ -17,11 +17,11 @@ namespace Discord.Rest


public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId);


internal RestApplication(DiscordRestClient discord, ulong id)
internal RestApplication(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestApplication Create(DiscordRestClient discord, Model model)
internal static RestApplication Create(DiscordClient discord, Model model)
{ {
var entity = new RestApplication(discord, model.Id); var entity = new RestApplication(discord, model.Id);
entity.Update(model); entity.Update(model);


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

@@ -5,10 +5,10 @@ namespace Discord.Rest
public abstract class RestEntity<T> : IEntity<T> public abstract class RestEntity<T> : IEntity<T>
where T : IEquatable<T> where T : IEquatable<T>
{ {
public DiscordClient Discord { get; }
public T Id { get; } public T Id { get; }
public DiscordRestClient Discord { get; }


public RestEntity(DiscordRestClient discord, T id)
internal RestEntity(DiscordClient discord, T id)
{ {
Discord = discord; Discord = discord;
Id = id; Id = id;


+ 2
- 2
src/Discord.Net.Rest/Entities/Roles/RestRole.cs View File

@@ -21,11 +21,11 @@ namespace Discord.Rest
public bool IsEveryone => Id == Guild.Id; public bool IsEveryone => Id == Guild.Id;
public string Mention => MentionUtils.MentionRole(Id); public string Mention => MentionUtils.MentionRole(Id);


internal RestRole(DiscordRestClient discord, ulong id)
internal RestRole(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestRole Create(DiscordRestClient discord, Model model)
internal static RestRole Create(DiscordClient discord, Model model)
{ {
var entity = new RestRole(discord, model.Id); var entity = new RestRole(discord, model.Id);
entity.Update(model); entity.Update(model);


+ 2
- 2
src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs View File

@@ -7,11 +7,11 @@ namespace Discord.Rest
internal static class RoleHelper internal static class RoleHelper
{ {
//General //General
public static async Task DeleteAsync(IRole role, DiscordRestClient client)
public static async Task DeleteAsync(IRole role, DiscordClient client)
{ {
await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false);
} }
public static async Task ModifyAsync(IRole role, DiscordRestClient client,
public static async Task ModifyAsync(IRole role, DiscordClient client,
Action<ModifyGuildRoleParams> func) Action<ModifyGuildRoleParams> func)
{ {
var args = new ModifyGuildRoleParams(); var args = new ModifyGuildRoleParams();


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

@@ -6,11 +6,11 @@ namespace Discord.Rest
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestGroupUser : RestUser, IGroupUser public class RestGroupUser : RestUser, IGroupUser
{ {
internal RestGroupUser(DiscordRestClient discord, ulong id)
internal RestGroupUser(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal new static RestGroupUser Create(DiscordRestClient discord, Model model)
internal new static RestGroupUser Create(DiscordClient discord, Model model)
{ {
var entity = new RestGroupUser(discord, model.Id); var entity = new RestGroupUser(discord, model.Id);
entity.Update(model); entity.Update(model);


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

@@ -21,11 +21,11 @@ namespace Discord.Rest
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);


internal RestGuildUser(DiscordRestClient discord, ulong id)
internal RestGuildUser(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestGuildUser Create(DiscordRestClient discord, Model model)
internal static RestGuildUser Create(DiscordClient discord, Model model)
{ {
var entity = new RestGuildUser(discord, model.User.Id); var entity = new RestGuildUser(discord, model.User.Id);
entity.Update(model); entity.Update(model);


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

@@ -13,11 +13,11 @@ namespace Discord.Rest
public bool IsVerified { get; private set; } public bool IsVerified { get; private set; }
public bool IsMfaEnabled { get; private set; } public bool IsMfaEnabled { get; private set; }


internal RestSelfUser(DiscordRestClient discord, ulong id)
internal RestSelfUser(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal new static RestSelfUser Create(DiscordRestClient discord, Model model)
internal new static RestSelfUser Create(DiscordClient discord, Model model)
{ {
var entity = new RestSelfUser(discord, model.Id); var entity = new RestSelfUser(discord, model.Id);
entity.Update(model); entity.Update(model);


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

@@ -18,11 +18,11 @@ namespace Discord.Rest
public virtual Game? Game => null; public virtual Game? Game => null;
public virtual UserStatus Status => UserStatus.Unknown; public virtual UserStatus Status => UserStatus.Unknown;


internal RestUser(DiscordRestClient discord, ulong id)
internal RestUser(DiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
internal static RestUser Create(DiscordRestClient discord, Model model)
internal static RestUser Create(DiscordClient discord, Model model)
{ {
var entity = new RestUser(discord, model.Id); var entity = new RestUser(discord, model.Id);
entity.Update(model); entity.Update(model);


+ 7
- 7
src/Discord.Net.Rest/Entities/Users/UserHelper.cs View File

@@ -8,22 +8,22 @@ namespace Discord.Rest
{ {
internal static class UserHelper internal static class UserHelper
{ {
public static async Task<Model> GetAsync(IUser user, DiscordRestClient client)
public static async Task<Model> GetAsync(IUser user, DiscordClient client)
{ {
return await client.ApiClient.GetUserAsync(user.Id); return await client.ApiClient.GetUserAsync(user.Id);
} }
public static async Task<Model> GetAsync(ISelfUser user, DiscordRestClient client)
public static async Task<Model> GetAsync(ISelfUser user, DiscordClient client)
{ {
var model = await client.ApiClient.GetMyUserAsync(); var model = await client.ApiClient.GetMyUserAsync();
if (model.Id != user.Id) if (model.Id != user.Id)
throw new InvalidOperationException("Unable to update this object using a different token."); throw new InvalidOperationException("Unable to update this object using a different token.");
return model; return model;
} }
public static async Task<MemberModel> GetAsync(IGuildUser user, DiscordRestClient client)
public static async Task<MemberModel> GetAsync(IGuildUser user, DiscordClient client)
{ {
return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id);
} }
public static async Task ModifyAsync(ISelfUser user, DiscordRestClient client, Action<ModifyCurrentUserParams> func)
public static async Task ModifyAsync(ISelfUser user, DiscordClient client, Action<ModifyCurrentUserParams> func)
{ {
if (user.Id != client.CurrentUser.Id) if (user.Id != client.CurrentUser.Id)
throw new InvalidOperationException("Unable to modify this object using a different token."); throw new InvalidOperationException("Unable to modify this object using a different token.");
@@ -32,19 +32,19 @@ namespace Discord.Rest
func(args); func(args);
await client.ApiClient.ModifySelfAsync(args); await client.ApiClient.ModifySelfAsync(args);
} }
public static async Task ModifyAsync(IGuildUser user, DiscordRestClient client, Action<ModifyGuildMemberParams> func)
public static async Task ModifyAsync(IGuildUser user, DiscordClient client, Action<ModifyGuildMemberParams> func)
{ {
var args = new ModifyGuildMemberParams(); var args = new ModifyGuildMemberParams();
func(args); func(args);
await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args);
} }


public static async Task KickAsync(IGuildUser user, DiscordRestClient client)
public static async Task KickAsync(IGuildUser user, DiscordClient client)
{ {
await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id);
} }


public static async Task<IDMChannel> CreateDMChannelAsync(IUser user, DiscordRestClient client)
public static async Task<IDMChannel> CreateDMChannelAsync(IUser user, DiscordClient client)
{ {
var args = new CreateDMChannelParams(user.Id); var args = new CreateDMChannelParams(user.Id);
return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args));


+ 0
- 6
src/Discord.Net.Rest/project.json View File

@@ -1,12 +1,6 @@
{ {
"version": "1.0.0-beta2-*", "version": "1.0.0-beta2-*",


"buildOptions": {
"compile": {
"include": [ "../Discord.Net.Utils/**.cs" ]
}
},

"configurations": { "configurations": {
"Release": { "Release": {
"buildOptions": { "buildOptions": {


+ 3
- 2
src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs View File

@@ -3,6 +3,7 @@ using Discord.API.Rpc;
using Discord.Net.Queue; using Discord.Net.Queue;
using Discord.Net.Rest; using Discord.Net.Rest;
using Discord.Net.WebSockets; using Discord.Net.WebSockets;
using Discord.Rpc;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
@@ -66,8 +67,8 @@ namespace Discord.API


public ConnectionState ConnectionState { get; private set; } public ConnectionState ConnectionState { get; private set; }


public DiscordRpcApiClient(string clientId, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null)
: base(restClientProvider, serializer, requestQueue)
public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null)
: base(restClientProvider, userAgent, serializer, requestQueue)
{ {
_connectionLock = new SemaphoreSlim(1, 1); _connectionLock = new SemaphoreSlim(1, 1);
_clientId = clientId; _clientId = clientId;


+ 3
- 0
src/Discord.Net.Rpc/AssemblyInfo.cs View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Discord.Net.Test")]

+ 7
- 18
src/Discord.Net.Rpc/DiscordRpcClient.cs View File

@@ -11,9 +11,9 @@ using System.Threading.Tasks;


namespace Discord.Rpc namespace Discord.Rpc
{ {
public partial class DiscordRpcClient : DiscordRestClient
public partial class DiscordRpcClient : DiscordClient
{ {
private readonly ILogger _rpcLogger;
private readonly Logger _rpcLogger;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;


private TaskCompletionSource<bool> _connectTask; private TaskCompletionSource<bool> _connectTask;
@@ -58,18 +58,7 @@ namespace Discord.Rpc
}; };
} }
private static API.DiscordRpcApiClient CreateApiClient(DiscordRpcConfig config) private static API.DiscordRpcApiClient CreateApiClient(DiscordRpcConfig config)
=> new API.DiscordRpcApiClient(config.ClientId, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue());

internal override void Dispose(bool disposing)
{
if (!_isDisposed)
ApiClient.Dispose();
}

protected override Task ValidateTokenAsync(TokenType tokenType, string token)
{
return Task.CompletedTask; //Validation is done in DiscordRpcAPIClient
}
=> new API.DiscordRpcApiClient(config.ClientId, DiscordRestConfig.UserAgent, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue());


/// <inheritdoc /> /// <inheritdoc />
public Task ConnectAsync() => ConnectAsync(false); public Task ConnectAsync() => ConnectAsync(false);
@@ -371,20 +360,20 @@ namespace Discord.Rpc
//Messages //Messages
case "MESSAGE_CREATE": case "MESSAGE_CREATE":
{ {
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);
/*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
var msg = new RpcMessage(this, data.Message); var msg = new RpcMessage(this, data.Message);


await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);
await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/
} }
break; break;
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
{ {
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);
/*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
var msg = new RpcMessage(this, data.Message); var msg = new RpcMessage(this, data.Message);


await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);
await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/
} }
break; break;
case "MESSAGE_DELETE": case "MESSAGE_DELETE":


+ 0
- 8
src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs View File

@@ -1,8 +0,0 @@
namespace Discord.Rpc
{
/*public interface IRemoteUserGuild : ISnowflakeEntity
{
/// <summary> Gets the name of this guild. </summary>
string Name { get; }
}*/
}

+ 10
- 0
src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs View File

@@ -0,0 +1,10 @@
namespace Discord.Rpc
{
/*internal class RpcMessage : RpcEntity<ulong>, IMessage
{
internal RpcMessage(DiscordRpcClient discord, API.Message model)
: base(dicsord, model.Id)
{
}
}*/
}

+ 19
- 0
src/Discord.Net.Rpc/Entities/RpcEntity.cs View File

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

namespace Discord.Rpc
{
public abstract class RpcEntity<T> : IEntity<T>
where T : IEquatable<T>
{
public DiscordRpcClient Discord { get; }
public T Id { get; }

internal RpcEntity(DiscordRpcClient discord, T id)
{
Discord = discord;
Id = id;
}

IDiscordClient IEntity<T>.Discord => Discord;
}
}

src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs → src/Discord.Net.Rpc/Entities/RpcGuild.cs View File

@@ -4,7 +4,7 @@ using Model = Discord.API.Rpc.RpcUserGuild;


namespace Discord.Rpc namespace Discord.Rpc
{ {
/*internal class RemoteUserGuild : IRemoteUserGuild, ISnowflakeEntity
/*internal class RemoteUserGuild : RpcEntity, IRemoteUserGuild, ISnowflakeEntity
{ {
public ulong Id { get; } public ulong Id { get; }
public DiscordRestClient Discord { get; } public DiscordRestClient Discord { get; }
@@ -12,7 +12,7 @@ namespace Discord.Rpc


public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id);


public RemoteUserGuild(DiscordRestClient discord, Model model)
internal RemoteUserGuild(DiscordRestClient discord, Model model)
{ {
Id = model.Id; Id = model.Id;
Discord = discord; Discord = discord;

+ 0
- 15
src/Discord.Net.Rpc/Entities/RpcMessage.cs View File

@@ -1,15 +0,0 @@
using Discord.Rest;

namespace Discord.Rpc
{
internal class RpcMessage : Message
{
public override DiscordRestClient Discord { get; }

public RpcMessage(DiscordRpcClient discord, API.Message model)
: base(null, model.Author.IsSpecified ? new User(model.Author.Value) : null, model)
{
Discord = discord;
}
}
}

+ 21
- 5
src/Discord.Net.Rpc/project.json View File

@@ -1,19 +1,35 @@
{ {
"version": "1.0.0-beta2-*", "version": "1.0.0-beta2-*",


"buildOptions": {
"compile": {
"include": [ "../Discord.Net.Utils/**.cs" ]
"configurations": {
"Release": {
"buildOptions": {
"define": [ "RELEASE" ],
"nowarn": [ "CS1573", "CS1591" ],
"optimize": true,
"warningsAsErrors": true,
"xmlDoc": true
}
} }
}, },


"dependencies": { "dependencies": {
"Discord.Net.Core": {
"target": "project"
},
"Discord.Net.Rest": {
"target": "project"
},
"NETStandard.Library": "1.6.0" "NETStandard.Library": "1.6.0"
}, },


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

+ 0
- 26
src/Discord.Net.Utils/Discord.Net.Utils.projitems View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>2b75119c-9893-4aaa-8d38-6176eeb09060</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Discord</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)AsyncEvent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConcurrentHashSet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DateTimeUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\CollectionExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\TaskCompletionSourceExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Logging\Logger.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Logging\LogManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)MentionsHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Paging\Page.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Paging\PagedEnumerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Paging\PageInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Permissions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Preconditions.cs" />
</ItemGroup>
</Project>

+ 0
- 13
src/Discord.Net.Utils/Discord.Net.Utils.shproj View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>2b75119c-9893-4aaa-8d38-6176eeb09060</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="Discord.Net.Utils.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

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

@@ -4,6 +4,7 @@ using Discord.API.Rest;
using Discord.Net.Queue; using Discord.Net.Queue;
using Discord.Net.Rest; using Discord.Net.Rest;
using Discord.Net.WebSockets; using Discord.Net.WebSockets;
using Discord.WebSocket;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -31,8 +32,8 @@ namespace Discord.API


public ConnectionState ConnectionState { get; private set; } public ConnectionState ConnectionState { get; private set; }


public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null)
: base(restClientProvider, serializer, requestQueue)
public DiscordSocketApiClient(RestClientProvider restClientProvider, string userAgent, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null)
: base(restClientProvider, userAgent, serializer, requestQueue)
{ {
_gatewayClient = webSocketProvider(); _gatewayClient = webSocketProvider();
//_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) //_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+)


+ 3
- 0
src/Discord.Net.WebSocket/AssemblyInfo.cs View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Discord.Net.Test")]

+ 4
- 23
src/Discord.Net.WebSocket/Audio/AudioClient.cs View File

@@ -5,6 +5,7 @@ using Discord.WebSocket;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
@@ -34,10 +35,7 @@ namespace Discord.Audio
} }
private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>();


private readonly ILogger _audioLogger;
#if BENCHMARK
private readonly ILogger _benchmarkLogger;
#endif
private readonly Logger _audioLogger;
internal readonly SemaphoreSlim _connectionLock; internal readonly SemaphoreSlim _connectionLock;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;


@@ -63,9 +61,6 @@ namespace Discord.Audio
Guild = guild; Guild = guild;


_audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); _audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}");
#if BENCHMARK
_benchmarkLogger = logManager.CreateLogger("Benchmark");
#endif


_connectionLock = new SemaphoreSlim(1, 1); _connectionLock = new SemaphoreSlim(1, 1);


@@ -181,11 +176,11 @@ namespace Discord.Audio
ApiClient.SendAsync(data, count).ConfigureAwait(false); ApiClient.SendAsync(data, count).ConfigureAwait(false);
} }


public RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000)
public Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000)
{ {
return new RTPWriteStream(this, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000); return new RTPWriteStream(this, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000);
} }
public OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null,
public Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null,
OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000) OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000)
{ {
return new OpusEncodeStream(this, _secretKey, samplesPerFrame, _ssrc, bitrate, application, bufferSize); return new OpusEncodeStream(this, _secretKey, samplesPerFrame, _ssrc, bitrate, application, bufferSize);
@@ -193,11 +188,6 @@ namespace Discord.Audio


private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
{ {
#if BENCHMARK
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
#endif
try try
{ {
switch (opCode) switch (opCode)
@@ -262,15 +252,6 @@ namespace Discord.Audio
await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false);
return; return;
} }
#if BENCHMARK
}
finally
{
stopwatch.Stop();
double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2);
await _benchmarkLogger.DebugAsync($"{millis} ms").ConfigureAwait(false);
}
#endif
} }
private async Task ProcessPacketAsync(byte[] packet) private async Task ProcessPacketAsync(byte[] packet)
{ {


+ 1
- 1
src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs View File

@@ -1,6 +1,6 @@
namespace Discord.Audio namespace Discord.Audio
{ {
public class OpusDecodeStream : RTPReadStream
internal class OpusDecodeStream : RTPReadStream
{ {
private readonly byte[] _buffer; private readonly byte[] _buffer;
private readonly OpusDecoder _decoder; private readonly OpusDecoder _decoder;


+ 1
- 1
src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs View File

@@ -1,6 +1,6 @@
namespace Discord.Audio namespace Discord.Audio
{ {
public class OpusEncodeStream : RTPWriteStream
internal class OpusEncodeStream : RTPWriteStream
{ {
public int SampleRate = 48000; public int SampleRate = 48000;
public int Channels = 2; public int Channels = 2;


+ 1
- 1
src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs View File

@@ -4,7 +4,7 @@ using System.IO;


namespace Discord.Audio namespace Discord.Audio
{ {
public class RTPReadStream : Stream
internal class RTPReadStream : Stream
{ {
private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer
private readonly AudioClient _audioClient; private readonly AudioClient _audioClient;


+ 1
- 1
src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs View File

@@ -3,7 +3,7 @@ using System.IO;


namespace Discord.Audio namespace Discord.Audio
{ {
public class RTPWriteStream : Stream
internal class RTPWriteStream : Stream
{ {
private readonly AudioClient _audioClient; private readonly AudioClient _audioClient;
private readonly byte[] _nonce, _secretKey; private readonly byte[] _nonce, _secretKey;


+ 11
- 11
src/Discord.Net.WebSocket/DataStore.cs View File

@@ -12,37 +12,37 @@ namespace Discord.WebSocket
private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 private const double AverageUsersPerGuild = 47.78; //Source: Googie2149
private const double CollectionMultiplier = 1.05; //Add 5% buffer to handle growth private const double CollectionMultiplier = 1.05; //Add 5% buffer to handle growth


private readonly ConcurrentDictionary<ulong, ISocketChannel> _channels;
private readonly ConcurrentDictionary<ulong, SocketChannel> _channels;
private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels;
private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds;
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users;
private readonly ConcurrentHashSet<ulong> _groupChannels; private readonly ConcurrentHashSet<ulong> _groupChannels;


internal IReadOnlyCollection<ISocketChannel> Channels => _channels.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection(); internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels);
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection();


internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels =>
_dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat(
_groupChannels.Select(x => GetChannel(x) as ISocketPrivateChannel))
internal IReadOnlyCollection<IPrivateChannel> PrivateChannels =>
_dmChannels.Select(x => x.Value as IPrivateChannel).Concat(
_groupChannels.Select(x => GetChannel(x) as IPrivateChannel))
.ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count);


public DataStore(int guildCount, int dmChannelCount) public DataStore(int guildCount, int dmChannelCount)
{ {
double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount;
double estimatedUsersCount = guildCount * AverageUsersPerGuild; double estimatedUsersCount = guildCount * AverageUsersPerGuild;
_channels = new ConcurrentDictionary<ulong, ISocketChannel>(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier));
_channels = new ConcurrentDictionary<ulong, SocketChannel>(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier));
_dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(CollectionConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); _dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(CollectionConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier));
_guilds = new ConcurrentDictionary<ulong, SocketGuild>(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); _guilds = new ConcurrentDictionary<ulong, SocketGuild>(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier));
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(CollectionConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); _users = new ConcurrentDictionary<ulong, SocketGlobalUser>(CollectionConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier));
_groupChannels = new ConcurrentHashSet<ulong>(CollectionConcurrencyLevel, (int)(10 * CollectionMultiplier)); _groupChannels = new ConcurrentHashSet<ulong>(CollectionConcurrencyLevel, (int)(10 * CollectionMultiplier));
} }


internal ISocketChannel GetChannel(ulong id)
internal SocketChannel GetChannel(ulong id)
{ {
ISocketChannel channel;
SocketChannel channel;
if (_channels.TryGetValue(id, out channel)) if (_channels.TryGetValue(id, out channel))
return channel; return channel;
return null; return null;
@@ -54,7 +54,7 @@ namespace Discord.WebSocket
return channel; return channel;
return null; return null;
} }
internal void AddChannel(ISocketChannel channel)
internal void AddChannel(SocketChannel channel)
{ {
_channels[channel.Id] = channel; _channels[channel.Id] = channel;


@@ -68,9 +68,9 @@ namespace Discord.WebSocket
_groupChannels.TryAdd(groupChannel.Id); _groupChannels.TryAdd(groupChannel.Id);
} }
} }
internal ISocketChannel RemoveChannel(ulong id)
internal SocketChannel RemoveChannel(ulong id)
{ {
ISocketChannel channel;
SocketChannel channel;
if (_channels.TryRemove(id, out channel)) if (_channels.TryRemove(id, out channel))
{ {
var dmChannel = channel as SocketDMChannel; var dmChannel = channel as SocketDMChannel;


+ 2
- 4
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj View File

@@ -4,18 +4,16 @@
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup> </PropertyGroup>

<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid> <ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid>
<RootNamespace>Discord.Net.WebSocket</RootNamespace>
<RootNamespace>Discord.WebSocket</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup> </PropertyGroup>

<PropertyGroup> <PropertyGroup>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

+ 105
- 163
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -1,4 +1,5 @@
using Discord.API.Gateway;
using Discord.API;
using Discord.API.Gateway;
using Discord.Audio; using Discord.Audio;
using Discord.Logging; using Discord.Logging;
using Discord.Net.Converters; using Discord.Net.Converters;
@@ -11,24 +12,22 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
public partial class DiscordSocketClient : DiscordRestClient, IDiscordClient
public partial class DiscordSocketClient : DiscordClient, IDiscordClient
{ {
private readonly ConcurrentQueue<ulong> _largeGuilds; private readonly ConcurrentQueue<ulong> _largeGuilds;
private readonly ILogger _gatewayLogger;
#if BENCHMARK
private readonly ILogger _benchmarkLogger;
#endif
private readonly Logger _gatewayLogger;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;


private string _sessionId; private string _sessionId;
private int _lastSeq; private int _lastSeq;
private ImmutableDictionary<string, VoiceRegion> _voiceRegions;
private ImmutableDictionary<string, RestVoiceRegion> _voiceRegions;
private TaskCompletionSource<bool> _connectTask; private TaskCompletionSource<bool> _connectTask;
private CancellationTokenSource _cancelToken, _reconnectCancelToken; private CancellationTokenSource _cancelToken, _reconnectCancelToken;
private Task _heartbeatTask, _guildDownloadTask, _reconnectTask; private Task _heartbeatTask, _guildDownloadTask, _reconnectTask;
@@ -54,16 +53,17 @@ namespace Discord.WebSocket
internal int ConnectionTimeout { get; private set; } internal int ConnectionTimeout { get; private set; }
internal WebSocketProvider WebSocketProvider { get; private set; } internal WebSocketProvider WebSocketProvider { get; private set; }


public new API.DiscordSocketApiClient ApiClient => base.ApiClient as API.DiscordSocketApiClient;
internal SocketSelfUser CurrentUser => _currentUser as SocketSelfUser;
public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
public new SocketSelfUser CurrentUser => base.CurrentUser as SocketSelfUser;
public IReadOnlyCollection<IPrivateChannel> PrivateChannels => DataStore.PrivateChannels;
internal IReadOnlyCollection<SocketGuild> Guilds => DataStore.Guilds; internal IReadOnlyCollection<SocketGuild> Guilds => DataStore.Guilds;
internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();


/// <summary> Creates a new REST/WebSocket discord client. </summary> /// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordSocketClient() : this(new DiscordSocketConfig()) { } public DiscordSocketClient() : this(new DiscordSocketConfig()) { }
/// <summary> Creates a new REST/WebSocket discord client. </summary> /// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordSocketClient(DiscordSocketConfig config)
: base(config, CreateApiClient(config))
public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config)) { }
private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client)
: base(config, client)
{ {
ShardId = config.ShardId; ShardId = config.ShardId;
TotalShards = config.TotalShards; TotalShards = config.TotalShards;
@@ -72,14 +72,10 @@ namespace Discord.WebSocket
AudioMode = config.AudioMode; AudioMode = config.AudioMode;
WebSocketProvider = config.WebSocketProvider; WebSocketProvider = config.WebSocketProvider;
ConnectionTimeout = config.ConnectionTimeout; ConnectionTimeout = config.ConnectionTimeout;

DataStore = new DataStore(0, 0); DataStore = new DataStore(0, 0);
_nextAudioId = 1; _nextAudioId = 1;

_gatewayLogger = LogManager.CreateLogger("Gateway"); _gatewayLogger = LogManager.CreateLogger("Gateway");
#if BENCHMARK
_benchmarkLogger = _log.CreateLogger("Benchmark");
#endif


_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer.Error += (s, e) => _serializer.Error += (s, e) =>
@@ -107,25 +103,25 @@ namespace Discord.WebSocket
GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false);
LatencyUpdated += async (old, val) => await _gatewayLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); LatencyUpdated += async (old, val) => await _gatewayLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false);


_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
_largeGuilds = new ConcurrentQueue<ulong>(); _largeGuilds = new ConcurrentQueue<ulong>();
} }
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue());
=> new API.DiscordSocketApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, config.WebSocketProvider, requestQueue: new RequestQueue());
protected override async Task OnLoginAsync(TokenType tokenType, string token) protected override async Task OnLoginAsync(TokenType tokenType, string token)
{ {
var voiceRegions = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); var voiceRegions = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false);
_voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id);
_voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id);
} }
protected override async Task OnLogoutAsync() protected override async Task OnLogoutAsync()
{ {
if (ConnectionState != ConnectionState.Disconnected) if (ConnectionState != ConnectionState.Disconnected)
await DisconnectInternalAsync(null, false).ConfigureAwait(false); await DisconnectInternalAsync(null, false).ConfigureAwait(false);


_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task ConnectAsync(bool waitForGuilds = true) public async Task ConnectAsync(bool waitForGuilds = true)
{ {
@@ -319,127 +315,55 @@ namespace Discord.WebSocket
} }


/// <inheritdoc /> /// <inheritdoc />
public override Task<IVoiceRegion> GetVoiceRegionAsync(string id)
{
VoiceRegion region;
if (_voiceRegions.TryGetValue(id, out region))
return Task.FromResult<IVoiceRegion>(region);
return Task.FromResult<IVoiceRegion>(null);
}
public Task<RestApplication> GetApplicationInfoAsync()
=> ClientHelper.GetApplicationInfoAsync(this);



/// <inheritdoc /> /// <inheritdoc />
public override Task<IGuild> GetGuildAsync(ulong id)
{
return Task.FromResult<IGuild>(DataStore.GetGuild(id));
}
public override Task<GuildEmbed?> GetGuildEmbedAsync(ulong id)
{
var guild = DataStore.GetGuild(id);
if (guild != null)
return Task.FromResult<GuildEmbed?>(new GuildEmbed(guild.IsEmbeddable, guild.EmbedChannelId));
else
return Task.FromResult<GuildEmbed?>(null);
}
public override Task<IReadOnlyCollection<IUserGuild>> GetGuildSummariesAsync()
{
return Task.FromResult<IReadOnlyCollection<IUserGuild>>(Guilds);
}
public override Task<IReadOnlyCollection<IGuild>> GetGuildsAsync()
public SocketGuild GetGuild(ulong id)
{ {
return Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds);
return DataStore.GetGuild(id);
} }
internal SocketGuild AddGuild(ExtendedGuild model, DataStore dataStore)
{
var guild = new SocketGuild(this, model, dataStore);
dataStore.AddGuild(guild);
if (model.Large)
_largeGuilds.Enqueue(model.Id);
return guild;
}
internal SocketGuild RemoveGuild(ulong id)
{
var guild = DataStore.RemoveGuild(id);
if (guild != null)
{
foreach (var channel in guild.Channels)
DataStore.RemoveChannel(id);
foreach (var user in guild.Members)
user.User.RemoveRef(this);
}
return guild;
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IChannel> GetChannelAsync(ulong id)
{
return Task.FromResult<IChannel>(DataStore.GetChannel(id));
}
public override Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync()
{
return Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(DataStore.PrivateChannels);
}
internal ISocketChannel AddPrivateChannel(API.Channel model, DataStore dataStore)
{
switch (model.Type)
{
case ChannelType.DM:
{
var recipients = model.Recipients.Value;
var user = GetOrAddUser(recipients[0], dataStore);
var channel = new SocketDMChannel(this, new SocketDMUser(user), model);
dataStore.AddChannel(channel);
return channel;
}
case ChannelType.Group:
{
var channel = new SocketGroupChannel(this, model);
channel.UpdateUsers(model.Recipients.Value, dataStore);
dataStore.AddChannel(channel);
return channel;
}
default:
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
}
}
internal ISocketChannel RemovePrivateChannel(ulong id)
{
var channel = DataStore.RemoveChannel(id) as ISocketPrivateChannel;
if (channel != null)
{
foreach (var recipient in channel.Recipients)
recipient.User.RemoveRef(this);
}
return channel;
}
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon);


/// <inheritdoc /> /// <inheritdoc />
public override Task<IUser> GetUserAsync(ulong id)
public IChannel GetChannel(ulong id)
{ {
return Task.FromResult<IUser>(DataStore.GetUser(id));
return DataStore.GetChannel(id);
} }

/// <inheritdoc /> /// <inheritdoc />
public override Task<IUser> GetUserAsync(string username, string discriminator)
{
return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault());
}
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
=> ClientHelper.GetConnectionsAsync(this);

/// <inheritdoc />
public Task<RestInvite> GetInviteAsync(string inviteId)
=> ClientHelper.GetInviteAsync(this, inviteId);

/// <inheritdoc /> /// <inheritdoc />
public override Task<ISelfUser> GetCurrentUserAsync()
public IUser GetUser(ulong id)
{ {
return Task.FromResult<ISelfUser>(_currentUser);
return DataStore.GetUser(id);
} }
internal SocketGlobalUser GetOrAddUser(API.User model, DataStore dataStore)
/// <inheritdoc />
public IUser GetUser(string username, string discriminator)
{ {
var user = dataStore.GetOrAddUser(model.Id, _ => new SocketGlobalUser(model));
user.AddRef();
return user;
return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault();
} }
internal SocketGlobalUser RemoveUser(ulong id)

/// <inheritdoc />
public RestVoiceRegion GetVoiceRegion(string id)
{ {
return DataStore.RemoveUser(id);
RestVoiceRegion region;
if (_voiceRegions.TryGetValue(id, out region))
return region;
return null;
} }


/// <summary> Downloads the users list for all large guilds. </summary> /// <summary> Downloads the users list for all large guilds. </summary>
public Task DownloadAllUsersAsync()
/*public Task DownloadAllUsersAsync()
=> DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers)); => DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers));
/// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary>
public Task DownloadUsersAsync(IEnumerable<IGuild> guilds) public Task DownloadUsersAsync(IEnumerable<IGuild> guilds)
@@ -490,20 +414,10 @@ namespace Discord.WebSocket
else else
await Task.WhenAll(batchTasks).ConfigureAwait(false); await Task.WhenAll(batchTasks).ConfigureAwait(false);
} }
}
}*/


public override Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync()
{
return Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(_voiceRegions.ToReadOnlyCollection());
}
private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload)
{ {
#if BENCHMARK
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
#endif
if (seq != null) if (seq != null)
_lastSeq = seq.Value; _lastSeq = seq.Value;
try try
@@ -516,7 +430,7 @@ namespace Discord.WebSocket
var data = (payload as JToken).ToObject<HelloEvent>(_serializer); var data = (payload as JToken).ToObject<HelloEvent>(_serializer);


_heartbeatTime = 0; _heartbeatTime = 0;
_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, _clientLogger);
_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, LogManager.ClientLogger);
} }
break; break;
case GatewayOpCode.Heartbeat: case GatewayOpCode.Heartbeat:
@@ -574,9 +488,9 @@ namespace Discord.WebSocket
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length); var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length);


var currentUser = new SocketSelfUser(this, data.User);
var currentUser = SocketSelfUser.Create(this, data.User);
int unavailableGuilds = 0; int unavailableGuilds = 0;
for (int i = 0; i < data.Guilds.Length; i++)
/*for (int i = 0; i < data.Guilds.Length; i++)
{ {
var model = data.Guilds[i]; var model = data.Guilds[i];
var guild = AddGuild(model, dataStore); var guild = AddGuild(model, dataStore);
@@ -586,10 +500,10 @@ namespace Discord.WebSocket
await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false);
} }
for (int i = 0; i < data.PrivateChannels.Length; i++) for (int i = 0; i < data.PrivateChannels.Length; i++)
AddPrivateChannel(data.PrivateChannels[i], dataStore);
AddPrivateChannel(data.PrivateChannels[i], dataStore);*/


_sessionId = data.SessionId; _sessionId = data.SessionId;
_currentUser = currentUser;
base.CurrentUser = currentUser;
_unavailableGuilds = unavailableGuilds; _unavailableGuilds = unavailableGuilds;
DataStore = dataStore; DataStore = dataStore;
} }
@@ -603,7 +517,7 @@ namespace Discord.WebSocket
await SyncGuildsAsync().ConfigureAwait(false); await SyncGuildsAsync().ConfigureAwait(false);


_lastGuildAvailableTime = Environment.TickCount; _lastGuildAvailableTime = Environment.TickCount;
_guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger);
_guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, LogManager.ClientLogger);


await _readyEvent.InvokeAsync().ConfigureAwait(false); await _readyEvent.InvokeAsync().ConfigureAwait(false);


@@ -611,7 +525,7 @@ namespace Discord.WebSocket
await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false);
} }
break; break;
case "RESUMED":
/*case "RESUMED":
{ {
await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false);


@@ -1366,7 +1280,7 @@ namespace Discord.WebSocket
} }
else else
{ {
before = new Presence(null, UserStatus.Offline);
before = new SocketPresence(null, UserStatus.Offline);
user = guild.AddOrUpdateUser(data, DataStore); user = guild.AddOrUpdateUser(data, DataStore);
} }


@@ -1430,7 +1344,7 @@ namespace Discord.WebSocket
if (data.GuildId.HasValue) if (data.GuildId.HasValue)
{ {
ISocketUser user; ISocketUser user;
VoiceState before, after;
SocketVoiceState before, after;
if (data.GuildId != null) if (data.GuildId != null)
{ {
var guild = DataStore.GetGuild(data.GuildId.Value); var guild = DataStore.GetGuild(data.GuildId.Value);
@@ -1444,7 +1358,7 @@ namespace Discord.WebSocket


if (data.ChannelId != null) if (data.ChannelId != null)
{ {
before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false);
before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false);
after = guild.AddOrUpdateVoiceState(data, DataStore); after = guild.AddOrUpdateVoiceState(data, DataStore);
if (data.UserId == _currentUser.Id) if (data.UserId == _currentUser.Id)
{ {
@@ -1453,8 +1367,8 @@ namespace Discord.WebSocket
} }
else else
{ {
before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false);
after = new VoiceState(null, data);
before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false);
after = new SocketVoiceState(null, data);
} }


user = guild.GetUser(data.UserId); user = guild.GetUser(data.UserId);
@@ -1472,13 +1386,13 @@ namespace Discord.WebSocket
{ {
if (data.ChannelId != null) if (data.ChannelId != null)
{ {
before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false);
before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false);
after = groupChannel.AddOrUpdateVoiceState(data, DataStore); after = groupChannel.AddOrUpdateVoiceState(data, DataStore);
} }
else else
{ {
before = groupChannel.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false);
after = new VoiceState(null, data);
before = groupChannel.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false);
after = new SocketVoiceState(null, data);
} }
user = groupChannel.GetUser(data.UserId); user = groupChannel.GetUser(data.UserId);
} }
@@ -1518,7 +1432,7 @@ namespace Discord.WebSocket
} }
} }


return;
return;*/


//Ignored (User only) //Ignored (User only)
case "CHANNEL_PINS_ACK": case "CHANNEL_PINS_ACK":
@@ -1550,18 +1464,9 @@ namespace Discord.WebSocket
await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false);
return; return;
} }
#if BENCHMARK
}
finally
{
stopwatch.Stop();
double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2);
await _benchmarkLogger.DebugAsync($"{millis} ms").ConfigureAwait(false);
}
#endif
} }


private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, ILogger logger)
private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, Logger logger)
{ {
try try
{ {
@@ -1601,7 +1506,7 @@ namespace Discord.WebSocket
await logger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); await logger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false);
} }
} }
private async Task WaitForGuildsAsync(CancellationToken cancelToken, ILogger logger)
private async Task WaitForGuildsAsync(CancellationToken cancelToken, Logger logger)
{ {
//Wait for GUILD_AVAILABLEs //Wait for GUILD_AVAILABLEs
try try
@@ -1626,5 +1531,42 @@ namespace Discord.WebSocket
if (guildIds.Length > 0) if (guildIds.Length > 0)
await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false);
} }

//IDiscordClient
DiscordRestApiClient IDiscordClient.ApiClient => ApiClient;

Task IDiscordClient.ConnectAsync()
=> ConnectAsync();

async Task<IApplication> IDiscordClient.GetApplicationInfoAsync()
=> await GetApplicationInfoAsync().ConfigureAwait(false);

Task<IChannel> IDiscordClient.GetChannelAsync(ulong id)
=> Task.FromResult<IChannel>(GetChannel(id));
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync()
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels);

async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync()
=> await GetConnectionsAsync();

async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId)
=> await GetInviteAsync(inviteId);

Task<IGuild> IDiscordClient.GetGuildAsync(ulong id)
=> Task.FromResult<IGuild>(GetGuild(id));
Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync()
=> Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds);
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon)
=> await CreateGuildAsync(name, region, jpegIcon);

Task<IUser> IDiscordClient.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(GetUser(id));
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator)
=> Task.FromResult<IUser>(GetUser(username, discriminator));

Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync()
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(_voiceRegions.ToReadOnlyCollection());
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id)
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id));
} }
} }

+ 45
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;

namespace Discord.WebSocket
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public abstract class SocketChannel : SocketEntity<ulong>, IChannel
{
internal SocketChannel(DiscordSocketClient discord, ulong id)
: base(discord, id)
{
}
internal static SocketChannel Create(DiscordSocketClient discord, Model model)
{
switch (model.Type)
{
case ChannelType.Text:
return SocketTextChannel.Create(discord, model);
case ChannelType.Voice:
return SocketVoiceChannel.Create(discord, model);
case ChannelType.DM:
return SocketDMChannel.Create(discord, model);
case ChannelType.Group:
return SocketGroupChannel.Create(discord, model);
default:
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
}
}

//IChannel
IReadOnlyCollection<IUser> IChannel.CachedUsers => ImmutableArray.Create<IUser>();

IUser IChannel.GetCachedUser(ulong id)
=> null;
Task<IUser> IChannel.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(null);
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable();
}
}

+ 102
- 41
src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs View File

@@ -1,77 +1,138 @@
using System.Collections.Generic;
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MessageModel = Discord.API.Message; using MessageModel = Discord.API.Message;
using Model = Discord.API.Channel; using Model = Discord.API.Channel;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class DMChannel : IDMChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketDMChannel : SocketChannel, IDMChannel
{ {
private readonly MessageManager _messages;
private readonly MessageCache _messages;


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new SocketDMUser Recipient => base.Recipient as SocketDMUser;
public IReadOnlyCollection<ISocketUser> Users => ImmutableArray.Create<ISocketUser>(Discord.CurrentUser, Recipient);
IReadOnlyCollection<ISocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient);
public SocketUser Recipient { get; private set; }


public SocketDMChannel(DiscordSocketClient discord, SocketDMUser recipient, Model model)
: base(discord, recipient, model)
public IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient);

internal SocketDMChannel(DiscordSocketClient discord, ulong id, ulong recipientId)
: base(discord, id)
{ {
Recipient = new SocketUser(Discord, recipientId);
if (Discord.MessageCacheSize > 0) if (Discord.MessageCacheSize > 0)
_messages = new MessageCache(Discord, this); _messages = new MessageCache(Discord, this);
else
_messages = new MessageManager(Discord, this);
} }
internal new static SocketDMChannel Create(DiscordSocketClient discord, Model model)
{
var entity = new SocketDMChannel(discord, model.Id, model.Recipients.Value[0].Id);
entity.Update(model);
return entity;
}
internal void Update(Model model)
{
Recipient.Update(model.Recipients.Value[0]);
}

public Task CloseAsync()
=> ChannelHelper.DeleteAsync(this, Discord);


public override Task<IUser> GetUserAsync(ulong id) => Task.FromResult<IUser>(GetUser(id));
public override Task<IReadOnlyCollection<IUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IUser>>(Users);
public ISocketUser GetUser(ulong id)
public SocketUser GetUser(ulong id)
{ {
var currentUser = Discord.CurrentUser;
if (id == Recipient.Id) if (id == Recipient.Id)
return Recipient; return Recipient;
else if (id == currentUser.Id)
return currentUser;
else if (id == Discord.CurrentUser.Id)
return Discord.CurrentUser as SocketSelfUser;
else else
return null; return null;
} }


public override async Task<IMessage> GetMessageAsync(ulong id)
{
return await _messages.DownloadAsync(id).ConfigureAwait(false);
}
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
{
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
}
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
{
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
}
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
public SocketMessage GetMessage(ulong id)
=> _messages?.Get(id);
public async Task<IMessage> GetMessageAsync(ulong id, bool allowDownload = true)
{ {
return _messages.Create(author, model);
IMessage msg = _messages?.Get(id);
if (msg == null && allowDownload)
msg = await ChannelHelper.GetMessageAsync(this, Discord, id);
return msg;
} }
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, limit: limit);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync()
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord);

public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS);

public Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages);

public IDisposable EnterTypingState()
=> ChannelHelper.EnterTypingState(this, Discord);

internal SocketMessage AddMessage(SocketUser author, MessageModel model)
{ {
var msg = _messages.Create(author, model);
var msg = SocketMessage.Create(Discord, author, model);
_messages.Add(msg); _messages.Add(msg);
return msg; return msg;
} }
public ISocketMessage GetMessage(ulong id)
{
return _messages.Get(id);
}
public ISocketMessage RemoveMessage(ulong id)
internal SocketMessage RemoveMessage(ulong id)
{ {
return _messages.Remove(id); return _messages.Remove(id);
} }


public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel;


IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id);
ISocketChannel ISocketChannel.Clone() => Clone();
public override string ToString() => $"@{Recipient}";
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)";

//IDMChannel
IUser IDMChannel.Recipient => Recipient;

//IPrivateChannel
IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient);

//IMessageChannel
IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>();
IMessage IMessageChannel.GetCachedMessage(ulong id) => null;

async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id)
=> await GetMessageAsync(id);
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit)
=> GetMessagesAsync(limit);
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
=> GetMessagesAsync(fromMessageId, dir, limit);
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync()
=> await GetPinnedMessagesAsync().ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS)
=> await SendFileAsync(filePath, text, isTTS);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS)
=> await SendFileAsync(stream, filename, text, isTTS);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS)
=> await SendMessageAsync(text, isTTS);
IDisposable IMessageChannel.EnterTypingState()
=> EnterTypingState();

//IChannel
IReadOnlyCollection<IUser> IChannel.CachedUsers => Users;

IUser IChannel.GetCachedUser(ulong id)
=> GetUser(id);
Task<IUser> IChannel.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(GetUser(id));
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable();
} }
} }

+ 98
- 105
src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs View File

@@ -1,7 +1,10 @@
using Discord.Rest; using Discord.Rest;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MessageModel = Discord.API.Message; using MessageModel = Discord.API.Message;
@@ -11,134 +14,124 @@ using VoiceStateModel = Discord.API.VoiceState;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketGroupChannel : IGroupChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketGroupChannel : SocketChannel, IGroupChannel
{ {
internal override bool IsAttached => true;
private readonly MessageCache _messages;


private readonly MessageManager _messages;
private ConcurrentDictionary<ulong, VoiceState> _voiceStates;
private string _iconId;
private ConcurrentDictionary<ulong, SocketGroupUser> _users;
private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates;


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public IReadOnlyCollection<ISocketUser> Users
=> _users.Select(x => x.Value as ISocketUser).Concat(ImmutableArray.Create(Discord.CurrentUser)).ToReadOnlyCollection(() => _users.Count + 1);
public new IReadOnlyCollection<ISocketUser> Recipients => _users.Select(x => x.Value as ISocketUser).ToReadOnlyCollection(_users);
public string Name { get; private set; }


public SocketGroupChannel(DiscordSocketClient discord, Model model)
: base(discord, model)
public IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection();
public IReadOnlyCollection<SocketGroupUser> Recipients
=> _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1);

internal SocketGroupChannel(DiscordSocketClient discord, ulong id)
: base(discord, id)
{ {
if (Discord.MessageCacheSize > 0) if (Discord.MessageCacheSize > 0)
_messages = new MessageCache(Discord, this); _messages = new MessageCache(Discord, this);
else
_messages = new MessageManager(Discord, this);
_voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, 5);
_voiceStates = new ConcurrentDictionary<ulong, SocketVoiceState>(1, 5);
_users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, 5);
} }
public override void Update(Model model)
internal new static SocketGroupChannel Create(DiscordSocketClient discord, Model model)
{ {
if (source == UpdateSource.Rest && IsAttached) return;
base.Update(model, source);
var entity = new SocketGroupChannel(discord, model.Id);
entity.Update(model);
return entity;
} }
internal void Update(Model model)
{
if (model.Name.IsSpecified)
Name = model.Name.Value;
if (model.Icon.IsSpecified)
_iconId = model.Icon.Value;


internal void UpdateUsers(UserModel[] models, DataStore dataStore)
if (model.Recipients.IsSpecified)
UpdateUsers(model.Recipients.Value);
}
internal virtual void UpdateUsers(API.User[] models)
{ {
var users = new ConcurrentDictionary<ulong, GroupUser>(1, models.Length);
var users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, (int)(models.Length * 1.05));
for (int i = 0; i < models.Length; i++) for (int i = 0; i < models.Length; i++)
{
var globalUser = Discord.GetOrAddUser(models[i], dataStore);
users[models[i].Id] = new SocketGroupUser(this, globalUser);
}
users[models[i].Id] = SocketGroupUser.Create(Discord, models[i]);
_users = users; _users = users;
} }
internal override void UpdateUsers(UserModel[] models)
=> UpdateUsers(models, source, Discord.DataStore);


public SocketGroupUser AddUser(UserModel model, DataStore dataStore)
{
GroupUser user;
if (_users.TryGetValue(model.Id, out user))
return user as SocketGroupUser;
else
{
var globalUser = Discord.GetOrAddUser(model, dataStore);
var privateUser = new SocketGroupUser(this, globalUser);
_users[privateUser.Id] = privateUser;
return privateUser;
}
}
public ISocketUser GetUser(ulong id)
public async Task UpdateAsync()
=> Update(await ChannelHelper.GetAsync(this, Discord));
public Task LeaveAsync()
=> ChannelHelper.DeleteAsync(this, Discord);

public SocketGroupUser GetUser(ulong id)
{ {
GroupUser user;
SocketGroupUser user;
if (_users.TryGetValue(id, out user)) if (_users.TryGetValue(id, out user))
return user as SocketGroupUser;
if (id == Discord.CurrentUser.Id)
return Discord.CurrentUser;
return null;
}
public SocketGroupUser RemoveUser(ulong id)
{
GroupUser user;
if (_users.TryRemove(id, out user))
return user as SocketGroupUser;
return user;
return null; return null;
} }


public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
{
var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel;
var voiceState = new VoiceState(voiceChannel, model);
(voiceStates ?? _voiceStates)[model.UserId] = voiceState;
return voiceState;
}
public VoiceState? GetVoiceState(ulong id)
{
VoiceState voiceState;
if (_voiceStates.TryGetValue(id, out voiceState))
return voiceState;
return null;
}
public VoiceState? RemoveVoiceState(ulong id)
{
VoiceState voiceState;
if (_voiceStates.TryRemove(id, out voiceState))
return voiceState;
return null;
}
public Task<RestMessage> GetMessageAsync(ulong id)
=> ChannelHelper.GetMessageAsync(this, Discord, id);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, limit: limit);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync()
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord);


public override async Task<IMessage> GetMessageAsync(ulong id)
{
return await _messages.DownloadAsync(id).ConfigureAwait(false);
}
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
{
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
}
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
{
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
}
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
{
return _messages.Create(author, model);
}
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
{
var msg = _messages.Create(author, model);
_messages.Add(msg);
return msg;
}
public ISocketMessage GetMessage(ulong id)
{
return _messages.Get(id);
}
public ISocketMessage RemoveMessage(ulong id)
{
return _messages.Remove(id);
}
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS);

public Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages);

public IDisposable EnterTypingState()
=> ChannelHelper.EnterTypingState(this, Discord);

//IPrivateChannel
IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients;

//IMessageChannel
IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>();

IMessage IMessageChannel.GetCachedMessage(ulong id)
=> null;
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id)
=> await GetMessageAsync(id);
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit)
=> GetMessagesAsync(limit);
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
=> GetMessagesAsync(fromMessageId, dir, limit);
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync()
=> await GetPinnedMessagesAsync();

async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS)
=> await SendFileAsync(filePath, text, isTTS);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS)
=> await SendFileAsync(stream, filename, text, isTTS);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS)
=> await SendMessageAsync(text, isTTS);
IDisposable IMessageChannel.EnterTypingState()
=> EnterTypingState();


public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel;
//IChannel
IReadOnlyCollection<IUser> IChannel.CachedUsers => Users;


IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id);
ISocketChannel ISocketChannel.Clone() => Clone();
IUser IChannel.GetCachedUser(ulong id)
=> GetUser(id);
Task<IUser> IChannel.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(GetUser(id));
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable();
} }
} }

+ 84
- 82
src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs View File

@@ -1,4 +1,5 @@
using Discord.API.Rest; using Discord.API.Rest;
using Discord.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -7,89 +8,59 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Channel; using Model = Discord.API.Channel;


namespace Discord.Rest
namespace Discord.WebSocket
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
internal abstract class SocketGuildChannel : ISnowflakeEntity, IGuildChannel
public abstract class SocketGuildChannel : SocketChannel, IGuildChannel
{ {
private List<Overwrite> _overwrites; //TODO: Is maintaining a list here too expensive? Is this threadsafe?
private ImmutableArray<Overwrite> _overwrites;


public string Name { get; private set; }
public int Position { get; private set; }
public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites;


public Guild Guild { get; private set; }
public ulong GuildId { get; }


public override DiscordRestClient Discord => Guild.Discord;
public string Name { get; private set; }
public int Position { get; private set; }


public GuildChannel(Guild guild, Model model)
: base(model.Id)
internal SocketGuildChannel(DiscordSocketClient discord, ulong id, ulong guildId)
: base(discord, id)
{ {
Guild = guild;

Update(model);
GuildId = guildId;
} }
public virtual void Update(Model model)
internal new static SocketGuildChannel Create(DiscordSocketClient discord, Model model)
{
switch (model.Type)
{
case ChannelType.Text:
return SocketTextChannel.Create(discord, model);
case ChannelType.Voice:
return SocketVoiceChannel.Create(discord, model);
default:
throw new InvalidOperationException("Unknown guild channel type");
}
}
internal virtual void Update(Model model)
{ {
if (source == UpdateSource.Rest && IsAttached) return;

Name = model.Name.Value; Name = model.Name.Value;
Position = model.Position.Value; Position = model.Position.Value;


var overwrites = model.PermissionOverwrites.Value; var overwrites = model.PermissionOverwrites.Value;
var newOverwrites = new List<Overwrite>(overwrites.Length);
var newOverwrites = ImmutableArray.CreateBuilder<Overwrite>(overwrites.Length);
for (int i = 0; i < overwrites.Length; i++) for (int i = 0; i < overwrites.Length; i++)
newOverwrites.Add(new Overwrite(overwrites[i])); newOverwrites.Add(new Overwrite(overwrites[i]));
_overwrites = newOverwrites;
_overwrites = newOverwrites.ToImmutable();
} }


public async Task UpdateAsync() public async Task UpdateAsync()
{
if (IsAttached) throw new NotSupportedException();

var model = await Discord.ApiClient.GetChannelAsync(Id).ConfigureAwait(false);
Update(model, UpdateSource.Rest);
}
public async Task ModifyAsync(Action<ModifyGuildChannelParams> func)
{
if (func == null) throw new NullReferenceException(nameof(func));

var args = new ModifyGuildChannelParams();
func(args);

if (!args._name.IsSpecified)
args._name = Name;

var model = await Discord.ApiClient.ModifyGuildChannelAsync(Id, args).ConfigureAwait(false);
Update(model, UpdateSource.Rest);
}
public async Task DeleteAsync()
{
await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false);
}

public abstract Task<IGuildUser> GetUserAsync(ulong id);
public abstract Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync();

public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync()
{
var models = await Discord.ApiClient.GetChannelInvitesAsync(Id).ConfigureAwait(false);
return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray();
}
public async Task<IInviteMetadata> CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary)
{
var args = new CreateChannelInviteParams
{
MaxAge = maxAge ?? 0,
MaxUses = maxUses ?? 0,
Temporary = isTemporary
};
var model = await Discord.ApiClient.CreateChannelInviteAsync(Id, args).ConfigureAwait(false);
return new InviteMetadata(Discord, model);
}
=> Update(await ChannelHelper.GetAsync(this, Discord));
public Task ModifyAsync(Action<ModifyGuildChannelParams> func)
=> ChannelHelper.ModifyAsync(this, Discord, func);
public Task DeleteAsync()
=> ChannelHelper.DeleteAsync(this, Discord);


public OverwritePermissions? GetPermissionOverwrite(IUser user) public OverwritePermissions? GetPermissionOverwrite(IUser user)
{ {
for (int i = 0; i < _overwrites.Count; i++)
for (int i = 0; i < _overwrites.Length; i++)
{ {
if (_overwrites[i].TargetId == user.Id) if (_overwrites[i].TargetId == user.Id)
return _overwrites[i].Permissions; return _overwrites[i].Permissions;
@@ -98,60 +69,91 @@ namespace Discord.Rest
} }
public OverwritePermissions? GetPermissionOverwrite(IRole role) public OverwritePermissions? GetPermissionOverwrite(IRole role)
{ {
for (int i = 0; i < _overwrites.Count; i++)
for (int i = 0; i < _overwrites.Length; i++)
{ {
if (_overwrites[i].TargetId == role.Id) if (_overwrites[i].TargetId == role.Id)
return _overwrites[i].Permissions; return _overwrites[i].Permissions;
} }
return null; return null;
} }
public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms) public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms)
{ {
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "member" };
await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, user.Id, args).ConfigureAwait(false);
_overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }));
await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms).ConfigureAwait(false);
_overwrites = _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }));
} }
public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms)
{ {
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "role" };
await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, role.Id, args).ConfigureAwait(false);
await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms).ConfigureAwait(false);
_overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }));
} }
public async Task RemovePermissionOverwriteAsync(IUser user) public async Task RemovePermissionOverwriteAsync(IUser user)
{ {
await Discord.ApiClient.DeleteChannelPermissionAsync(Id, user.Id).ConfigureAwait(false);
await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user).ConfigureAwait(false);


for (int i = 0; i < _overwrites.Count; i++)
for (int i = 0; i < _overwrites.Length; i++)
{ {
if (_overwrites[i].TargetId == user.Id) if (_overwrites[i].TargetId == user.Id)
{ {
_overwrites.RemoveAt(i);
_overwrites = _overwrites.RemoveAt(i);
return; return;
} }
} }
} }
public async Task RemovePermissionOverwriteAsync(IRole role) public async Task RemovePermissionOverwriteAsync(IRole role)
{ {
await Discord.ApiClient.DeleteChannelPermissionAsync(Id, role.Id).ConfigureAwait(false);
await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role).ConfigureAwait(false);


for (int i = 0; i < _overwrites.Count; i++)
for (int i = 0; i < _overwrites.Length; i++)
{ {
if (_overwrites[i].TargetId == role.Id) if (_overwrites[i].TargetId == role.Id)
{ {
_overwrites.RemoveAt(i);
_overwrites = _overwrites.RemoveAt(i);
return; return;
} }
} }
} }
public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})";
IGuild IGuildChannel.Guild => Guild;
IReadOnlyCollection<Overwrite> IGuildChannel.PermissionOverwrites => _overwrites.AsReadOnly();


async Task<IUser> IChannel.GetUserAsync(ulong id) => await GetUserAsync(id).ConfigureAwait(false);
async Task<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() => await GetUsersAsync().ConfigureAwait(false);
public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync()
=> await ChannelHelper.GetInvitesAsync(this, Discord);
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary);

//IGuildChannel
async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync()
=> await GetInvitesAsync();
async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary)
=> await CreateInviteAsync(maxAge, maxUses, isTemporary);

OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role)
=> GetPermissionOverwrite(role);
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user)
=> GetPermissionOverwrite(user);
async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions)
=> await AddPermissionOverwriteAsync(role, permissions);
async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions)
=> await AddPermissionOverwriteAsync(user, permissions);
async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role)
=> await RemovePermissionOverwriteAsync(role);
async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user)
=> await RemovePermissionOverwriteAsync(user);

IReadOnlyCollection<IGuildUser> IGuildChannel.CachedUsers
=> ImmutableArray.Create<IGuildUser>();
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override?
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id)
=> Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice //TODO: Does this actually override?
IGuildUser IGuildChannel.GetCachedUser(ulong id)
=> null;

//IChannel
IReadOnlyCollection<IUser> IChannel.CachedUsers
=> ImmutableArray.Create<IUser>();
IUser IChannel.GetCachedUser(ulong id)
=> null;
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override?
Task<IUser> IChannel.GetUserAsync(ulong id)
=> Task.FromResult<IUser>(null); //Overriden in Text/Voice //TODO: Does this actually override?
} }
} }

+ 82
- 54
src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs View File

@@ -1,6 +1,10 @@
using Discord.Rest;
using Discord.API.Rest;
using Discord.Rest;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MessageModel = Discord.API.Message; using MessageModel = Discord.API.Message;
@@ -8,81 +12,105 @@ using Model = Discord.API.Channel;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketTextChannel : TextChannel, ISocketGuildChannel, ISocketMessageChannel
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketTextChannel : SocketGuildChannel, ITextChannel
{ {
internal override bool IsAttached => true;
private readonly MessageCache _messages;


private readonly MessageManager _messages;
public string Topic { get; private set; }


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new SocketGuild Guild => base.Guild as SocketGuild;
public string Mention => MentionUtils.MentionChannel(Id);


public IReadOnlyCollection<SocketGuildUser> Members
=> Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();

public SocketTextChannel(SocketGuild guild, Model model)
: base(guild, model)
internal SocketTextChannel(DiscordSocketClient discord, ulong id, ulong guildId)
: base(discord, id, guildId)
{ {
if (Discord.MessageCacheSize > 0) if (Discord.MessageCacheSize > 0)
_messages = new MessageCache(Discord, this); _messages = new MessageCache(Discord, this);
else
_messages = new MessageManager(Discord, this);
}

public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id));
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
public SocketGuildUser GetUser(ulong id, bool skipCheck = false)
{
var user = Guild.GetUser(id);
if (skipCheck) return user;

if (user != null)
{
ulong perms = Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue);
if (Permissions.GetValue(perms, ChannelPermission.ReadMessages))
return user;
}
return null;
}

public override async Task<IMessage> GetMessageAsync(ulong id)
{
return await _messages.DownloadAsync(id).ConfigureAwait(false);
} }
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch)
internal new static SocketTextChannel Create(DiscordSocketClient discord, Model model)
{ {
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
var entity = new SocketTextChannel(discord, model.Id, model.GuildId.Value);
entity.Update(model);
return entity;
} }
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
internal override void Update(Model model)
{ {
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
}
base.Update(model);


public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
{
return _messages.Create(author, model);
Topic = model.Topic.Value;
} }
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)

public Task ModifyAsync(Action<ModifyTextChannelParams> func)
=> ChannelHelper.ModifyAsync(this, Discord, func);

public Task<RestGuildUser> GetUserAsync(ulong id)
=> ChannelHelper.GetUserAsync(this, Discord, id);
public IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync()
=> ChannelHelper.GetUsersAsync(this, Discord);

public Task<RestMessage> GetMessageAsync(ulong id)
=> ChannelHelper.GetMessageAsync(this, Discord, id);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, limit: limit);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync()
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord);

public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS);

public Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages);

public IDisposable EnterTypingState()
=> ChannelHelper.EnterTypingState(this, Discord);

internal SocketMessage AddMessage(SocketUser author, MessageModel model)
{ {
var msg = _messages.Create(author, model);
var msg = SocketMessage.Create(Discord, author, model);
_messages.Add(msg); _messages.Add(msg);
return msg; return msg;
} }
public ISocketMessage GetMessage(ulong id)
{
return _messages.Get(id);
}
public ISocketMessage RemoveMessage(ulong id)
internal SocketMessage RemoveMessage(ulong id)
{ {
return _messages.Remove(id); return _messages.Remove(id);
} }


public SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel;
public override string ToString() => Name;
private string DebuggerDisplay => $"@{Name} ({Id}, Text)";

//IGuildChannel
async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id)
=> await GetUserAsync(id);
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync()
=> GetUsersAsync();


IReadOnlyCollection<ISocketUser> ISocketMessageChannel.Users => Members;
//IMessageChannel
IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>();
IMessage IMessageChannel.GetCachedMessage(ulong id) => null;


IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id, skipCheck);
ISocketChannel ISocketChannel.Clone() => Clone();
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id)
=> await GetMessageAsync(id);
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit)
=> GetMessagesAsync(limit);
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
=> GetMessagesAsync(fromMessageId, dir, limit);
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync()
=> await GetPinnedMessagesAsync().ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS)
=> await SendFileAsync(filePath, text, isTTS);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS)
=> await SendFileAsync(stream, filename, text, isTTS);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS)
=> await SendMessageAsync(text, isTTS);
IDisposable IMessageChannel.EnterTypingState()
=> EnterTypingState();
} }
} }

+ 28
- 32
src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs View File

@@ -1,54 +1,50 @@
using Discord.Audio;
using Discord.API.Rest;
using Discord.Audio;
using Discord.Rest; using Discord.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Channel; using Model = Discord.API.Channel;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketVoiceChannel : VoiceChannel, ISocketGuildChannel
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel
{ {
internal override bool IsAttached => true;
public int Bitrate { get; private set; }
public int UserLimit { get; private set; }


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new SocketGuild Guild => base.Guild as SocketGuild;

public IReadOnlyCollection<IGuildUser> Members
=> Guild.VoiceStates.Where(x => x.Value.VoiceChannel.Id == Id).Select(x => Guild.GetUser(x.Key)).ToImmutableArray();

public SocketVoiceChannel(SocketGuild guild, Model model)
: base(guild, model)
internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, ulong guildId)
: base(discord, id, guildId)
{ {
} }

public override Task<IGuildUser> GetUserAsync(ulong id)
=> Task.FromResult(GetUser(id));
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync()
=> Task.FromResult(Members);
public IGuildUser GetUser(ulong id)
internal new static SocketVoiceChannel Create(DiscordSocketClient discord, Model model)
{ {
var user = Guild.GetUser(id);
if (user != null && user.VoiceChannel.Id == Id)
return user;
return null;
var entity = new SocketVoiceChannel(discord, model.Id, model.GuildId.Value);
entity.Update(model);
return entity;
} }

public override async Task<IAudioClient> ConnectAsync()
internal override void Update(Model model)
{ {
var audioMode = Discord.AudioMode;
if (audioMode == AudioMode.Disabled)
throw new InvalidOperationException($"Audio is not enabled on this client, {nameof(DiscordSocketConfig.AudioMode)} in {nameof(DiscordSocketConfig)} must be set.");
return await Guild.ConnectAudioAsync(Id,
(audioMode & AudioMode.Incoming) == 0,
(audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false);
base.Update(model);

Bitrate = model.Bitrate.Value;
UserLimit = model.UserLimit.Value;
} }


public SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;
public Task ModifyAsync(Action<ModifyVoiceChannelParams> func)
=> ChannelHelper.ModifyAsync(this, Discord, func);

//IVoiceChannel
Task<IAudioClient> IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); }


ISocketChannel ISocketChannel.Clone() => Clone();
//IGuildChannel
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id)
=> Task.FromResult<IGuildUser>(null);
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync()
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable();
} }
} }

+ 188
- 385
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -1,4 +1,5 @@
using Discord.Audio;
using Discord.API.Rest;
using Discord.Audio;
using Discord.Rest; using Discord.Rest;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -19,402 +20,204 @@ using VoiceStateModel = Discord.API.VoiceState;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketGuild : Guild, IGuild, IUserGuild
public class SocketGuild : SocketEntity<ulong>, IGuild
{ {
internal override bool IsAttached => true;

private readonly SemaphoreSlim _audioLock;
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
private TaskCompletionSource<AudioClient> _audioConnectPromise;
private ConcurrentHashSet<ulong> _channels;
private ConcurrentDictionary<ulong, SocketGuildUser> _members;
private ConcurrentDictionary<ulong, VoiceState> _voiceStates;
private ImmutableDictionary<ulong, RestRole> _roles;
private ImmutableArray<Emoji> _emojis;
private ImmutableArray<string> _features;
internal bool _available; internal bool _available;


public bool Available => _available && Discord.ConnectionState == ConnectionState.Connected;
public int MemberCount { get; set; }
public int DownloadedMemberCount { get; private set; }
public AudioClient AudioClient { get; private set; }

public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
public bool IsSynced => _syncPromise.Task.IsCompleted;
public Task SyncPromise => _syncPromise.Task;
public Task DownloaderPromise => _downloaderPromise.Task;

public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public SocketGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id);
public IReadOnlyCollection<ISocketGuildChannel> Channels
{
get
{
var channels = _channels;
var store = Discord.DataStore;
return channels.Select(x => store.GetChannel(x) as ISocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels);
}
}
public IReadOnlyCollection<SocketGuildUser> Members => _members.ToReadOnlyCollection();
public IEnumerable<KeyValuePair<ulong, VoiceState>> VoiceStates => _voiceStates;
public SocketGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
{
_audioLock = new SemaphoreSlim(1, 1);
_syncPromise = new TaskCompletionSource<bool>();
_downloaderPromise = new TaskCompletionSource<bool>();
Update(model, dataStore);
}

public void Update(ExtendedModel model, DataStore dataStore)
{
if (source == UpdateSource.Rest && IsAttached) return;

_available = !(model.Unavailable ?? false);
if (!_available)
{
if (_channels == null)
_channels = new ConcurrentHashSet<ulong>();
if (_members == null)
_members = new ConcurrentDictionary<ulong, SocketGuildUser>();
if (_roles == null)
_roles = new ConcurrentDictionary<ulong, Role>();
if (Emojis == null)
Emojis = ImmutableArray.Create<Emoji>();
if (Features == null)
Features = ImmutableArray.Create<string>();
return;
}

base.Update(model as Model, source);
var channels = new ConcurrentHashSet<ulong>(1, (int)(model.Channels.Length * 1.05));
{
for (int i = 0; i < model.Channels.Length; i++)
AddChannel(model.Channels[i], dataStore, channels);
}
_channels = channels;

var members = new ConcurrentDictionary<ulong, SocketGuildUser>(1, (int)(model.Presences.Length * 1.05));
{
DownloadedMemberCount = 0;
for (int i = 0; i < model.Members.Length; i++)
AddOrUpdateUser(model.Members[i], dataStore, members);
if (Discord.ApiClient.AuthTokenType != TokenType.User)
{
var _ = _syncPromise.TrySetResultAsync(true);
if (!model.Large)
_ = _downloaderPromise.TrySetResultAsync(true);
}

for (int i = 0; i < model.Presences.Length; i++)
AddOrUpdateUser(model.Presences[i], dataStore, members);
}
_members = members;
MemberCount = model.MemberCount;

var voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, (int)(model.VoiceStates.Length * 1.05));
{
for (int i = 0; i < model.VoiceStates.Length; i++)
AddOrUpdateVoiceState(model.VoiceStates[i], dataStore, voiceStates);
}
_voiceStates = voiceStates;
}
public void Update(GuildSyncModel model, DataStore dataStore)
{
if (source == UpdateSource.Rest && IsAttached) return;

var members = new ConcurrentDictionary<ulong, SocketGuildUser>(1, (int)(model.Presences.Length * 1.05));
{
DownloadedMemberCount = 0;
for (int i = 0; i < model.Members.Length; i++)
AddOrUpdateUser(model.Members[i], dataStore, members);
var _ = _syncPromise.TrySetResultAsync(true);
if (!model.Large)
_ = _downloaderPromise.TrySetResultAsync(true);

for (int i = 0; i < model.Presences.Length; i++)
AddOrUpdateUser(model.Presences[i], dataStore, members);
public string Name { get; private set; }
public int AFKTimeout { get; private set; }
public bool IsEmbeddable { get; private set; }
public VerificationLevel VerificationLevel { get; private set; }
public MfaLevel MfaLevel { get; private set; }
public DefaultMessageNotifications DefaultMessageNotifications { get; private set; }

public ulong? AFKChannelId { get; private set; }
public ulong? EmbedChannelId { get; private set; }
public ulong OwnerId { get; private set; }
public string VoiceRegionId { get; private set; }
public string IconId { get; private set; }
public string SplashId { get; private set; }

public ulong DefaultChannelId => Id;
public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId);
public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId);
public bool IsSynced => false;

public RestRole EveryoneRole => GetRole(Id);
public IReadOnlyCollection<RestRole> Roles => _roles.ToReadOnlyCollection();
public IReadOnlyCollection<Emoji> Emojis => _emojis;
public IReadOnlyCollection<string> Features => _features;

internal SocketGuild(DiscordSocketClient client, ulong id)
: base(client, id)
{
}
internal static SocketGuild Create(DiscordSocketClient discord, Model model)
{
var entity = new SocketGuild(discord, model.Id);
entity.Update(model);
return entity;
}
internal void Update(Model model)
{
AFKChannelId = model.AFKChannelId;
EmbedChannelId = model.EmbedChannelId;
AFKTimeout = model.AFKTimeout;
IsEmbeddable = model.EmbedEnabled;
IconId = model.Icon;
Name = model.Name;
OwnerId = model.OwnerId;
VoiceRegionId = model.Region;
SplashId = model.Splash;
VerificationLevel = model.VerificationLevel;
MfaLevel = model.MfaLevel;
DefaultMessageNotifications = model.DefaultMessageNotifications;

if (model.Emojis != null)
{
var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length);
for (int i = 0; i < model.Emojis.Length; i++)
emojis.Add(Emoji.Create(model.Emojis[i]));
_emojis = emojis.ToImmutableArray();
} }
_members = members;
}

public void Update(EmojiUpdateModel model)
{
if (source == UpdateSource.Rest && IsAttached) return;
var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length);
for (int i = 0; i < model.Emojis.Length; i++)
emojis.Add(new Emoji(model.Emojis[i]));
Emojis = emojis.ToImmutableArray();
}

public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id));
public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels);
public ISocketGuildChannel AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null)
{
var channel = ToChannel(model);
(channels ?? _channels).TryAdd(model.Id);
dataStore.AddChannel(channel);
return channel;
}
public ISocketGuildChannel GetChannel(ulong id)
{
return Discord.DataStore.GetChannel(id) as ISocketGuildChannel;
}
public ISocketGuildChannel RemoveChannel(ulong id)
{
_channels.TryRemove(id);
return Discord.DataStore.RemoveChannel(id) as ISocketGuildChannel;
}
public Role AddRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null)
{
var role = new Role(this, model);
(roles ?? _roles)[model.Id] = role;
return role;
}
public Role RemoveRole(ulong id)
{
Role role;
if (_roles.TryRemove(id, out role))
return role;
return null;
}

public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id));
public override Task<IGuildUser> GetCurrentUserAsync()
=> Task.FromResult<IGuildUser>(CurrentUser);
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync()
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
public SocketGuildUser AddOrUpdateUser(MemberModel model, DataStore dataStore, ConcurrentDictionary<ulong, SocketGuildUser> members = null)
{
members = members ?? _members;

SocketGuildUser member;
if (members.TryGetValue(model.User.Id, out member))
member.Update(model, UpdateSource.WebSocket);
else else
{
var user = Discord.GetOrAddUser(model.User, dataStore);
member = new SocketGuildUser(this, user, model);
members[user.Id] = member;
DownloadedMemberCount++;
}
return member;
}
public SocketGuildUser AddOrUpdateUser(PresenceModel model, DataStore dataStore, ConcurrentDictionary<ulong, SocketGuildUser> members = null)
{
members = members ?? _members;
_emojis = ImmutableArray.Create<Emoji>();


SocketGuildUser member;
if (members.TryGetValue(model.User.Id, out member))
member.Update(model, UpdateSource.WebSocket);
if (model.Features != null)
_features = model.Features.ToImmutableArray();
else else
{
var user = Discord.GetOrAddUser(model.User, dataStore);
member = new SocketGuildUser(this, user, model);
members[user.Id] = member;
DownloadedMemberCount++;
}
return member;
}
public SocketGuildUser GetUser(ulong id)
{
SocketGuildUser member;
if (_members.TryGetValue(id, out member))
return member;
return null;
}
public SocketGuildUser RemoveUser(ulong id)
{
SocketGuildUser member;
if (_members.TryRemove(id, out member))
{
DownloadedMemberCount--;
return member;
}
member.User.RemoveRef(Discord);
_features = ImmutableArray.Create<string>();

var roles = ImmutableDictionary.CreateBuilder<ulong, RestRole>();
if (model.Roles != null)
{
throw new NotImplementedException();
}
_roles = roles.ToImmutable();
}

//General
public async Task UpdateAsync()
=> Update(await Discord.ApiClient.GetGuildAsync(Id));
public Task DeleteAsync()
=> GuildHelper.DeleteAsync(this, Discord);

public Task ModifyAsync(Action<ModifyGuildParams> func)
=> GuildHelper.ModifyAsync(this, Discord, func);
public Task ModifyEmbedAsync(Action<ModifyGuildEmbedParams> func)
=> GuildHelper.ModifyEmbedAsync(this, Discord, func);
public Task ModifyChannelsAsync(IEnumerable<ModifyGuildChannelsParams> args)
=> GuildHelper.ModifyChannelsAsync(this, Discord, args);
public Task ModifyRolesAsync(IEnumerable<ModifyGuildRolesParams> args)
=> GuildHelper.ModifyRolesAsync(this, Discord, args);

public Task LeaveAsync()
=> GuildHelper.LeaveAsync(this, Discord);

//Bans
public Task<IReadOnlyCollection<RestBan>> GetBansAsync()
=> GuildHelper.GetBansAsync(this, Discord);

public Task AddBanAsync(IUser user, int pruneDays = 0)
=> GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays);
public Task AddBanAsync(ulong userId, int pruneDays = 0)
=> GuildHelper.AddBanAsync(this, Discord, userId, pruneDays);

public Task RemoveBanAsync(IUser user)
=> GuildHelper.RemoveBanAsync(this, Discord, user.Id);
public Task RemoveBanAsync(ulong userId)
=> GuildHelper.RemoveBanAsync(this, Discord, userId);

//Channels
public Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync()
=> GuildHelper.GetChannelsAsync(this, Discord);
public Task<RestGuildChannel> GetChannelAsync(ulong id)
=> GuildHelper.GetChannelAsync(this, Discord, id);
public Task<RestTextChannel> CreateTextChannelAsync(string name)
=> GuildHelper.CreateTextChannelAsync(this, Discord, name);
public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name)
=> GuildHelper.CreateVoiceChannelAsync(this, Discord, name);

//Integrations
public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync()
=> GuildHelper.GetIntegrationsAsync(this, Discord);
public Task<RestGuildIntegration> CreateIntegrationAsync(ulong id, string type)
=> GuildHelper.CreateIntegrationAsync(this, Discord, id, type);

//Invites
public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync()
=> GuildHelper.GetInvitesAsync(this, Discord);

//Roles
public RestRole GetRole(ulong id)
{
RestRole value;
if (_roles.TryGetValue(id, out value))
return value;
return null; return null;
} }
public override async Task DownloadUsersAsync()
{
await Discord.DownloadUsersAsync(new [] { this });
}
public void CompleteDownloadMembers()
{
_downloaderPromise.TrySetResultAsync(true);
}


public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
public async Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false)
{ {
var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel;
var voiceState = new VoiceState(voiceChannel, model);
(voiceStates ?? _voiceStates)[model.UserId] = voiceState;
return voiceState;
}
public VoiceState? GetVoiceState(ulong id)
{
VoiceState voiceState;
if (_voiceStates.TryGetValue(id, out voiceState))
return voiceState;
return null;
}
public VoiceState? RemoveVoiceState(ulong id)
{
VoiceState voiceState;
if (_voiceStates.TryRemove(id, out voiceState))
return voiceState;
return null;
}

public async Task<IAudioClient> ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute)
{
try
{
TaskCompletionSource<AudioClient> promise;

await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectAudioInternalAsync().ConfigureAwait(false);
promise = new TaskCompletionSource<AudioClient>();
_audioConnectPromise = promise;
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
}
finally
{
_audioLock.Release();
}

var timeoutTask = Task.Delay(15000);
if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask)
throw new TimeoutException();
return await promise.Task.ConfigureAwait(false);
}
catch (Exception)
{
await DisconnectAudioInternalAsync().ConfigureAwait(false);
throw;
}
}
public async Task DisconnectAudioAsync(AudioClient client = null)
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectAudioInternalAsync(client).ConfigureAwait(false);
}
finally
{
_audioLock.Release();
}
}
private async Task DisconnectAudioInternalAsync(AudioClient client = null)
{
var oldClient = AudioClient;
if (oldClient != null)
{
if (client == null || oldClient == client)
{
_audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection
_audioConnectPromise = null;
}
if (oldClient == client)
{
AudioClient = null;
await oldClient.DisconnectAsync().ConfigureAwait(false);
}
}
}
public async Task FinishConnectAudio(int id, string url, string token)
{
var voiceState = GetVoiceState(CurrentUser.Id).Value;

await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (AudioClient == null)
{
var audioClient = new AudioClient(this, id);
audioClient.Disconnected += async ex =>
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client
{
if (ex != null)
{
//Reconnect if we still have channel info.
//TODO: Is this threadsafe? Could channel data be deleted before we access it?
var voiceState2 = GetVoiceState(CurrentUser.Id);
if (voiceState2.HasValue)
{
var voiceChannelId = voiceState2.Value.VoiceChannel?.Id;
if (voiceChannelId != null)
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted);
}
}
else
{
try { AudioClient.Dispose(); } catch { }
AudioClient = null;
}
}
}
finally
{
_audioLock.Release();
}
};
AudioClient = audioClient;
}
await AudioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
await DisconnectAudioAsync();
}
catch (Exception e)
{
await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false);
await DisconnectAudioAsync();
}
finally
{
_audioLock.Release();
}
}
public async Task FinishJoinAudioChannel()
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (AudioClient != null)
await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false);
}
finally
{
_audioLock.Release();
}
}

public SocketGuild Clone() => MemberwiseClone() as SocketGuild;

new internal ISocketGuildChannel ToChannel(ChannelModel model)
{
switch (model.Type)
{
case ChannelType.Text:
return new SocketTextChannel(this, model);
case ChannelType.Voice:
return new SocketVoiceChannel(this, model);
default:
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
}
var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted);
_roles = _roles.Add(role.Id, role);
return role;
} }


bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id;
GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions;
IAudioClient IGuild.AudioClient => AudioClient;
//Users
public Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync()
=> GuildHelper.GetUsersAsync(this, Discord);
public Task<RestGuildUser> GetUserAsync(ulong id)
=> GuildHelper.GetUserAsync(this, Discord, id);
public Task<RestGuildUser> GetCurrentUserAsync()
=> GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id);

public Task<int> PruneUsersAsync(int days = 30, bool simulate = false)
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate);

//IGuild
bool IGuild.Available => true;
IAudioClient IGuild.AudioClient => null;
IReadOnlyCollection<IGuildUser> IGuild.CachedUsers => ImmutableArray.Create<IGuildUser>();
IRole IGuild.EveryoneRole => EveryoneRole;
IReadOnlyCollection<IRole> IGuild.Roles => Roles;

async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync()
=> await GetBansAsync();

async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync()
=> await GetChannelsAsync();
async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id)
=> await GetChannelAsync(id);
IGuildChannel IGuild.GetCachedChannel(ulong id)
=> null;
async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name)
=> await CreateTextChannelAsync(name);
async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name)
=> await CreateVoiceChannelAsync(name);

async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync()
=> await GetIntegrationsAsync();
async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type)
=> await CreateIntegrationAsync(id, type);

async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync()
=> await GetInvitesAsync();

IRole IGuild.GetRole(ulong id)
=> GetRole(id);

async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync()
=> await GetUsersAsync();
async Task<IGuildUser> IGuild.GetUserAsync(ulong id)
=> await GetUserAsync(id);
IGuildUser IGuild.GetCachedUser(ulong id)
=> null;
async Task<IGuildUser> IGuild.GetCurrentUserAsync()
=> await GetCurrentUserAsync();
Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); }
} }
} }

+ 0
- 76
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs View File

@@ -1,76 +0,0 @@
using Discord.API.Rest;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Model = Discord.API.Integration;

namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
internal class GuildIntegration : IEntity<ulong>, IGuildIntegration
{
private long _syncedAtTicks;

public string Name { get; private set; }
public string Type { get; private set; }
public bool IsEnabled { get; private set; }
public bool IsSyncing { get; private set; }
public ulong ExpireBehavior { get; private set; }
public ulong ExpireGracePeriod { get; private set; }

public Guild Guild { get; private set; }
public Role Role { get; private set; }
public User User { get; private set; }
public IntegrationAccount Account { get; private set; }

public override DiscordRestClient Discord => Guild.Discord;
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks);

public GuildIntegration(Guild guild, Model model)
: base(model.Id)
{
Guild = guild;
Update(model);
}

public void Update(Model model)
{
Name = model.Name;
Type = model.Type;
IsEnabled = model.Enabled;
IsSyncing = model.Syncing;
ExpireBehavior = model.ExpireBehavior;
ExpireGracePeriod = model.ExpireGracePeriod;
_syncedAtTicks = model.SyncedAt.UtcTicks;

Role = Guild.GetRole(model.RoleId);
User = new User(model.User);
}

public async Task DeleteAsync()
{
await Discord.ApiClient.DeleteGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false);
}
public async Task ModifyAsync(Action<ModifyGuildIntegrationParams> func)
{
if (func == null) throw new NullReferenceException(nameof(func));

var args = new ModifyGuildIntegrationParams();
func(args);
var model = await Discord.ApiClient.ModifyGuildIntegrationAsync(Guild.Id, Id, args).ConfigureAwait(false);

Update(model, UpdateSource.Rest);
}
public async Task SyncAsync()
{
await Discord.ApiClient.SyncGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false);
}

public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})";

IGuild IGuildIntegration.Guild => Guild;
IUser IGuildIntegration.User => User;
IRole IGuildIntegration.Role => Role;
}
}

+ 0
- 13
src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs View File

@@ -1,13 +0,0 @@
using Model = Discord.API.Message;

namespace Discord.WebSocket
{
internal interface ISocketMessage : IMessage
{
DiscordSocketClient Discord { get; }
new ISocketMessageChannel Channel { get; }

void Update(Model model);
ISocketMessage Clone();
}
}

src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs → src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs View File

@@ -1,4 +1,6 @@
using System;
using Discord.Rest;
using Discord.WebSocket;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -7,53 +9,52 @@ using System.Threading.Tasks;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class MessageCache : MessageManager
internal class MessageCache
{ {
private readonly ConcurrentDictionary<ulong, ISocketMessage> _messages;
private readonly ConcurrentDictionary<ulong, SocketMessage> _messages;
private readonly ConcurrentQueue<ulong> _orderedMessages; private readonly ConcurrentQueue<ulong> _orderedMessages;
private readonly int _size; private readonly int _size;


public override IReadOnlyCollection<ISocketMessage> Messages => _messages.ToReadOnlyCollection();
public IReadOnlyCollection<SocketMessage> Messages => _messages.ToReadOnlyCollection();


public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel)
: base(discord, channel)
public MessageCache(DiscordSocketClient discord, IChannel channel)
{ {
_size = discord.MessageCacheSize; _size = discord.MessageCacheSize;
_messages = new ConcurrentDictionary<ulong, ISocketMessage>(1, (int)(_size * 1.05));
_messages = new ConcurrentDictionary<ulong, SocketMessage>(1, (int)(_size * 1.05));
_orderedMessages = new ConcurrentQueue<ulong>(); _orderedMessages = new ConcurrentQueue<ulong>();
} }


public override void Add(ISocketMessage message)
public void Add(SocketMessage message)
{ {
if (_messages.TryAdd(message.Id, message)) if (_messages.TryAdd(message.Id, message))
{ {
_orderedMessages.Enqueue(message.Id); _orderedMessages.Enqueue(message.Id);


ulong msgId; ulong msgId;
ISocketMessage msg;
SocketMessage msg;
while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId))
_messages.TryRemove(msgId, out msg); _messages.TryRemove(msgId, out msg);
} }
} }


public override ISocketMessage Remove(ulong id)
public SocketMessage Remove(ulong id)
{ {
ISocketMessage msg;
SocketMessage msg;
_messages.TryRemove(id, out msg); _messages.TryRemove(id, out msg);
return msg; return msg;
} }


public override ISocketMessage Get(ulong id)
public SocketMessage Get(ulong id)
{ {
ISocketMessage result;
SocketMessage result;
if (_messages.TryGetValue(id, out result)) if (_messages.TryGetValue(id, out result))
return result; return result;
return null; return null;
} }
public override IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
public IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
{ {
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (limit == 0) return ImmutableArray<ISocketMessage>.Empty;
if (limit == 0) return ImmutableArray<SocketMessage>.Empty;


IEnumerable<ulong> cachedMessageIds; IEnumerable<ulong> cachedMessageIds;
if (fromMessageId == null) if (fromMessageId == null)
@@ -67,7 +68,7 @@ namespace Discord.WebSocket
.Take(limit) .Take(limit)
.Select(x => .Select(x =>
{ {
ISocketMessage msg;
SocketMessage msg;
if (_messages.TryGetValue(x, out msg)) if (_messages.TryGetValue(x, out msg))
return msg; return msg;
return null; return null;
@@ -75,13 +76,5 @@ namespace Discord.WebSocket
.Where(x => x != null) .Where(x => x != null)
.ToImmutableArray(); .ToImmutableArray();
} }

public override async Task<ISocketMessage> DownloadAsync(ulong id)
{
var msg = Get(id);
if (msg != null)
return msg;
return await base.DownloadAsync(id).ConfigureAwait(false);
}
} }
} }

+ 64
- 0
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading.Tasks;
using Model = Discord.API.Message;

namespace Discord.WebSocket
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public abstract class SocketMessage : SocketEntity<ulong>, IMessage, IUpdateable
{
private long _timestampTicks;

public ulong ChannelId { get; }
public SocketUser Author { get; }

public string Content { get; private set; }

public virtual bool IsTTS => false;
public virtual bool IsPinned => false;
public virtual DateTimeOffset? EditedTimestamp => null;

public virtual IReadOnlyCollection<IAttachment> Attachments => ImmutableArray.Create<IAttachment>();
public virtual IReadOnlyCollection<IEmbed> Embeds => ImmutableArray.Create<IEmbed>();
public virtual IReadOnlyCollection<ulong> MentionedChannelIds => ImmutableArray.Create<ulong>();
public virtual IReadOnlyCollection<IRole> MentionedRoles => ImmutableArray.Create<IRole>();
public virtual IReadOnlyCollection<IUser> MentionedUsers => ImmutableArray.Create<IUser>();

public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);

internal SocketMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author)
: base(discord, id)
{
ChannelId = channelId;
Author = author;
}
internal static SocketMessage Create(DiscordSocketClient discord, SocketUser author, Model model)
{
if (model.Type == MessageType.Default)
return SocketUserMessage.Create(discord, author, model);
else
return SocketSystemMessage.Create(discord, author, model);
}
internal virtual void Update(Model model)
{
if (model.Timestamp.IsSpecified)
_timestampTicks = model.Timestamp.Value.UtcTicks;

if (model.Content.IsSpecified)
Content = model.Content.Value;
}

public async Task UpdateAsync()
{
var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id).ConfigureAwait(false);
Update(model);
}

//IMessage
IUser IMessage.Author => Author;
MessageType IMessage.Type => MessageType.Default;
}
}

+ 15
- 8
src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs View File

@@ -3,18 +3,25 @@ using Model = Discord.API.Message;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketSystemMessage : SystemMessage, ISocketMessage
internal class SocketSystemMessage : SocketMessage, ISystemMessage
{ {
internal override bool IsAttached => true;
public MessageType Type { get; private set; }


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel;
public SocketSystemMessage(ISocketMessageChannel channel, IUser author, Model model)
: base(channel, author, model)
internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author)
: base(discord, id, channelId, author)
{
}
internal new static SocketSystemMessage Create(DiscordSocketClient discord, SocketUser author, Model model)
{ {
var entity = new SocketSystemMessage(discord, model.Id, model.ChannelId, author);
entity.Update(model);
return entity;
} }
internal override void Update(Model model)
{
base.Update(model);


public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
Type = model.Type;
}
} }
} }

+ 121
- 8
src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs View File

@@ -1,20 +1,133 @@
using Discord.Rest;
using Discord.API.Rest;
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Model = Discord.API.Message; using Model = Discord.API.Message;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketUserMessage : UserMessage, ISocketMessage
internal class SocketUserMessage : SocketMessage, IUserMessage
{ {
internal override bool IsAttached => true;
private bool _isMentioningEveryone, _isTTS, _isPinned;
private long? _editedTimestampTicks;
private ImmutableArray<RestAttachment> _attachments;
private ImmutableArray<RestEmbed> _embeds;
private ImmutableArray<ulong> _mentionedChannelIds;
private ImmutableArray<RestRole> _mentionedRoles;
private ImmutableArray<RestUser> _mentionedUsers;


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel;
public override bool IsTTS => _isTTS;
public override bool IsPinned => _isPinned;
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);


public SocketUserMessage(ISocketMessageChannel channel, IUser author, Model model)
: base(channel, author, model)
public override IReadOnlyCollection<IAttachment> Attachments => _attachments;
public override IReadOnlyCollection<IEmbed> Embeds => _embeds;
public override IReadOnlyCollection<ulong> MentionedChannelIds => _mentionedChannelIds;
public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles;
public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers;

internal SocketUserMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author)
: base(discord, id, channelId, author)
{
}
internal new static SocketUserMessage Create(DiscordSocketClient discord, SocketUser author, Model model)
{
var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author);
entity.Update(model);
return entity;
}

internal override void Update(Model model)
{ {
base.Update(model);

if (model.IsTextToSpeech.IsSpecified)
_isTTS = model.IsTextToSpeech.Value;
if (model.Pinned.IsSpecified)
_isPinned = model.Pinned.Value;
if (model.EditedTimestamp.IsSpecified)
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks;
if (model.MentionEveryone.IsSpecified)
_isMentioningEveryone = model.MentionEveryone.Value;

if (model.Attachments.IsSpecified)
{
var value = model.Attachments.Value;
if (value.Length > 0)
{
var attachments = ImmutableArray.CreateBuilder<RestAttachment>(value.Length);
for (int i = 0; i < value.Length; i++)
attachments.Add(RestAttachment.Create(value[i]));
_attachments = attachments.ToImmutable();
}
else
_attachments = ImmutableArray.Create<RestAttachment>();
}

if (model.Embeds.IsSpecified)
{
var value = model.Embeds.Value;
if (value.Length > 0)
{
var embeds = ImmutableArray.CreateBuilder<RestEmbed>(value.Length);
for (int i = 0; i < value.Length; i++)
embeds.Add(RestEmbed.Create(value[i]));
_embeds = embeds.ToImmutable();
}
else
_embeds = ImmutableArray.Create<RestEmbed>();
}

ImmutableArray<RestUser> mentions = ImmutableArray.Create<RestUser>();
if (model.Mentions.IsSpecified)
{
var value = model.Mentions.Value;
if (value.Length > 0)
{
var newMentions = ImmutableArray.CreateBuilder<RestUser>(value.Length);
for (int i = 0; i < value.Length; i++)
newMentions.Add(RestUser.Create(Discord, value[i]));
mentions = newMentions.ToImmutable();
}
}

if (model.Content.IsSpecified)
{
var text = model.Content.Value;

_mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions);
_mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null);
_mentionedRoles = MentionsHelper.GetRoleMentions<RestRole>(text, null);
model.Content = text;
}
} }


public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
public Task ModifyAsync(Action<ModifyMessageParams> func)
=> MessageHelper.ModifyAsync(this, Discord, func);
public Task DeleteAsync()
=> MessageHelper.DeleteAsync(this, Discord);

public Task PinAsync()
=> MessageHelper.PinAsync(this, Discord);
public Task UnpinAsync()
=> MessageHelper.UnpinAsync(this, Discord);

public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore)
=> Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling);
public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore)
=> Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling);
public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
{
text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling);
text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling);
text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling);
text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling);
return text;
}
} }
} }

+ 19
- 0
src/Discord.Net.WebSocket/Entities/SocketEntity.cs View File

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

namespace Discord.WebSocket
{
public abstract class SocketEntity<T> : IEntity<T>
where T : IEquatable<T>
{
public DiscordSocketClient Discord { get; }
public T Id { get; }

internal SocketEntity(DiscordSocketClient discord, T id)
{
Discord = discord;
Id = id;
}

IDiscordClient IEntity<T>.Discord => Discord;
}
}

+ 0
- 9
src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs View File

@@ -1,9 +0,0 @@
namespace Discord.WebSocket
{
internal interface ISocketUser : IUser, IEntity<ulong>
{
SocketGlobalUser User { get; }

ISocketUser Clone();
}
}

+ 0
- 17
src/Discord.Net.WebSocket/Entities/Users/Presence.cs View File

@@ -1,17 +0,0 @@
namespace Discord.WebSocket
{
//TODO: C#7 Candidate for record type
internal struct Presence : IPresence
{
public Game Game { get; }
public UserStatus Status { get; }

public Presence(Game game, UserStatus status)
{
Game = game;
Status = status;
}

public Presence Clone() => this;
}
}

+ 0
- 46
src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs View File

@@ -1,46 +0,0 @@
using System;
using System.Diagnostics;
using PresenceModel = Discord.API.Presence;

namespace Discord.WebSocket
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal class SocketDMUser : ISocketUser
{
internal bool IsAttached => true;
bool IEntity<ulong>.IsAttached => IsAttached;

public SocketGlobalUser User { get; }

public DiscordSocketClient Discord => User.Discord;

public Game Game => Presence.Game;
public UserStatus Status => Presence.Status;
public Presence Presence => User.Presence; //{ get; private set; }

public ulong Id => User.Id;
public string AvatarUrl => User.AvatarUrl;
public DateTimeOffset CreatedAt => User.CreatedAt;
public string Discriminator => User.Discriminator;
public ushort DiscriminatorValue => User.DiscriminatorValue;
public bool IsBot => User.IsBot;
public string Mention => MentionUtils.Mention(this);
public string Username => User.Username;

public SocketDMUser(SocketGlobalUser user)
{
User = user;
}

public void Update(PresenceModel model)
{
User.Update(model, source);
}

public SocketDMUser Clone() => MemberwiseClone() as SocketDMUser;
ISocketUser ISocketUser.Clone() => Clone();

public override string ToString() => $"{Username}#{Discriminator}";
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})";
}
}

+ 4
- 55
src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs View File

@@ -1,61 +1,10 @@
using Discord.Rest;
using System;
using Model = Discord.API.User;
using PresenceModel = Discord.API.Presence;

namespace Discord.WebSocket
namespace Discord.WebSocket
{ {
internal class SocketGlobalUser : User, ISocketUser
internal class SocketGlobalUser : SocketUser
{ {
internal override bool IsAttached => true;
private readonly object _lockObj = new object();

private ushort _references;

public Presence Presence { get; private set; }

public new DiscordSocketClient Discord { get { throw new NotSupportedException(); } }
SocketGlobalUser ISocketUser.User => this;

public SocketGlobalUser(Model model)
: base(model)
internal SocketGlobalUser(DiscordSocketClient discord, ulong id)
: base(discord, id)
{ {
} }

public void AddRef()
{
checked
{
lock (_lockObj)
_references++;
}
}
public void RemoveRef(DiscordSocketClient discord)
{
lock (_lockObj)
{
if (--_references == 0)
discord.RemoveUser(Id);
}
}

public override void Update(Model model)
{
lock (_lockObj)
base.Update(model, source);
}
public void Update(PresenceModel model)
{
//Race conditions are okay here. Multiple shards racing already cant guarantee presence in order.
//lock (_lockObj)
//{
var game = model.Game != null ? new Game(model.Game) : null;
Presence = new Presence(game, model.Status);
//}
}

public SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser;
ISocketUser ISocketUser.Clone() => Clone();
} }
} }

+ 18
- 24
src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs View File

@@ -1,36 +1,30 @@
using Discord.Rest; using Discord.Rest;
using System.Diagnostics; using System.Diagnostics;
using Model = Discord.API.User;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
internal class SocketGroupUser : GroupUser, ISocketUser
public class SocketGroupUser : SocketUser, IGroupUser
{ {
internal override bool IsAttached => true;

public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new SocketGroupChannel Channel => base.Channel as SocketGroupChannel;
public new SocketGlobalUser User => base.User as SocketGlobalUser;
public Presence Presence => User.Presence; //{ get; private set; }

public override Game Game => Presence.Game;
public override UserStatus Status => Presence.Status;

public VoiceState? VoiceState => Channel.GetVoiceState(Id);
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false;
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false;
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false;
public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel;

public SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser user)
: base(channel, user)
internal SocketGroupUser(DiscordSocketClient discord, ulong id)
: base(discord, id)
{ {
} }
internal new static SocketGroupUser Create(DiscordSocketClient discord, Model model)
{
var entity = new SocketGroupUser(discord, model.Id);
entity.Update(model);
return entity;
}


public SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser;
ISocketUser ISocketUser.Clone() => Clone();

public override string ToString() => $"{Username}#{Discriminator}";
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})";
//IVoiceState
bool IVoiceState.IsDeafened => false;
bool IVoiceState.IsMuted => false;
bool IVoiceState.IsSelfDeafened => false;
bool IVoiceState.IsSelfMuted => false;
bool IVoiceState.IsSuppressed => false;
IVoiceChannel IVoiceState.VoiceChannel => null;
string IVoiceState.VoiceSessionId => null;
} }
} }

+ 56
- 35
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs View File

@@ -1,53 +1,74 @@
using Discord.Rest;
using Discord.API.Rest;
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Model = Discord.API.GuildMember; using Model = Discord.API.GuildMember;
using PresenceModel = Discord.API.Presence; using PresenceModel = Discord.API.Presence;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
internal class SocketGuildUser : GuildUser, ISocketUser, IVoiceState
internal class SocketGuildUser : SocketUser, IGuildUser
{ {
internal override bool IsAttached => true;

public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new SocketGuild Guild => base.Guild as SocketGuild;
public new SocketGlobalUser User => base.User as SocketGlobalUser;
public Presence Presence => User.Presence; //{ get; private set; }

public override Game Game => Presence.Game;
public override UserStatus Status => Presence.Status;

public VoiceState? VoiceState => Guild.GetVoiceState(Id);
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false;
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false;
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false;
public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel;
public bool IsDeafened => VoiceState?.IsDeafened ?? false;
public bool IsMuted => VoiceState?.IsMuted ?? false;
public string VoiceSessionId => VoiceState?.VoiceSessionId ?? "";

public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, Model model)
: base(guild, user, model)
private long? _joinedAtTicks;
private ImmutableArray<ulong> _roleIds;

public string Nickname { get; private set; }
public ulong GuildId { get; private set; }

public IReadOnlyCollection<ulong> RoleIds => _roleIds;

public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);

internal SocketGuildUser(DiscordSocketClient discord, ulong id)
: base(discord, id)
{ {
//Presence = new Presence(null, UserStatus.Offline);
} }
public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, PresenceModel model)
: base(guild, user, model)
internal static SocketGuildUser Create(DiscordSocketClient discord, Model model)
{ {
var entity = new SocketGuildUser(discord, model.User.Id);
entity.Update(model);
return entity;
} }

public override void Update(PresenceModel model)
internal void Update(Model model)
{ {
base.Update(model, source);
_joinedAtTicks = model.JoinedAt.UtcTicks;
if (model.Nick.IsSpecified)
Nickname = model.Nick.Value;
UpdateRoles(model.Roles);
}
private void UpdateRoles(ulong[] roleIds)
{
var roles = ImmutableArray.CreateBuilder<ulong>(roleIds.Length + 1);
roles.Add(GuildId);
for (int i = 0; i < roleIds.Length; i++)
roles.Add(roleIds[i]);
_roleIds = roles.ToImmutable();
}


var game = model.Game != null ? new Game(model.Game) : null;
//Presence = new Presence(game, model.Status);
public override async Task UpdateAsync()
=> Update(await UserHelper.GetAsync(this, Discord));
public Task ModifyAsync(Action<ModifyGuildMemberParams> func)
=> UserHelper.ModifyAsync(this, Discord, func);
public Task KickAsync()
=> UserHelper.KickAsync(this, Discord);


User.Update(model, source);
public ChannelPermissions GetPermissions(IGuildChannel channel)
{
throw new NotImplementedException(); //TODO: Impl
} }


IVoiceChannel IVoiceState.VoiceChannel => VoiceState?.VoiceChannel;
//IGuildUser
IReadOnlyCollection<ulong> IGuildUser.RoleIds => RoleIds;


public SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser;
ISocketUser ISocketUser.Clone() => Clone();
//IVoiceState
bool IVoiceState.IsDeafened => false;
bool IVoiceState.IsMuted => false;
bool IVoiceState.IsSelfDeafened => false;
bool IVoiceState.IsSelfMuted => false;
bool IVoiceState.IsSuppressed => false;
IVoiceChannel IVoiceState.VoiceChannel => null;
string IVoiceState.VoiceSessionId => null;
} }
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save