Started converting websocket and rpc classes
This commit is contained in:
@@ -15,8 +15,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Dis
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Discord.Net.Utils\Discord.Net.Utils.shproj", "{2B75119C-9893-4AAA-8D38-6176EEB09060}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}"
|
||||
EndProject
|
||||
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}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
|
||||
7
src/Discord.Net.Core/AssemblyInfo.cs
Normal file
7
src/Discord.Net.Core/AssemblyInfo.cs
Normal 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")]
|
||||
@@ -14,7 +14,7 @@ namespace Discord
|
||||
public bool RequireColons { 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;
|
||||
Name = name;
|
||||
@@ -22,7 +22,7 @@ namespace Discord
|
||||
RequireColons = requireColons;
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ namespace Discord
|
||||
public string Name { get; }
|
||||
public string Url { get; }
|
||||
|
||||
public EmbedProvider(string name, string url)
|
||||
private EmbedProvider(string name, string url)
|
||||
{
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
public static EmbedProvider Create(Model model)
|
||||
internal static EmbedProvider Create(Model model)
|
||||
{
|
||||
return new EmbedProvider(model.Name, model.Url);
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace Discord
|
||||
public int? Height { 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;
|
||||
ProxyUrl = proxyUrl;
|
||||
Height = height;
|
||||
Width = width;
|
||||
}
|
||||
public static EmbedThumbnail Create(Model model)
|
||||
internal static EmbedThumbnail Create(Model model)
|
||||
{
|
||||
return new EmbedThumbnail(model.Url, model.ProxyUrl,
|
||||
model.Height.IsSpecified ? model.Height.Value : (int?)null,
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Discord
|
||||
StreamUrl = streamUrl;
|
||||
StreamType = type;
|
||||
}
|
||||
public Game(string name)
|
||||
private Game(string name)
|
||||
: this(name, null, StreamType.NotStreaming) { }
|
||||
public static Game Create(Model model)
|
||||
internal static Game Create(Model model)
|
||||
{
|
||||
return new Game(model.Name,
|
||||
model.StreamUrl.GetValueOrDefault(null),
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
Unknown,
|
||||
Online,
|
||||
Idle,
|
||||
DoNotDisturb,
|
||||
Invisible,
|
||||
Offline
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
public static string Sanitize(string text)
|
||||
{
|
||||
foreach (string unsafeChar in SensitiveCharacters)
|
||||
{
|
||||
text = text.Replace(unsafeChar, $"\\{unsafeChar}");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,12 @@ namespace Discord
|
||||
|
||||
Task<IGuild> GetGuildAsync(ulong id);
|
||||
Task<IReadOnlyCollection<IGuild>> GetGuildsAsync();
|
||||
Task<IReadOnlyCollection<IUserGuild>> GetGuildSummariesAsync();
|
||||
Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null);
|
||||
|
||||
Task<IInvite> GetInviteAsync(string inviteId);
|
||||
|
||||
Task<IUser> GetUserAsync(ulong id);
|
||||
Task<IUser> GetUserAsync(string username, string discriminator);
|
||||
Task<IReadOnlyCollection<IUser>> QueryUsersAsync(string query, int limit);
|
||||
|
||||
Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync();
|
||||
Task<IVoiceRegion> GetVoiceRegionAsync(string id);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Logging
|
||||
@@ -6,6 +7,7 @@ namespace Discord.Logging
|
||||
internal class LogManager
|
||||
{
|
||||
public LogSeverity Level { get; }
|
||||
public Logger ClientLogger { get; }
|
||||
|
||||
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>>();
|
||||
@@ -13,6 +15,7 @@ namespace Discord.Logging
|
||||
public LogManager(LogSeverity minSeverity)
|
||||
{
|
||||
Level = minSeverity;
|
||||
ClientLogger = new Logger(this, "Discord");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@ namespace Discord.Net.Converters
|
||||
return UserStatus.Online;
|
||||
case "idle":
|
||||
return UserStatus.Idle;
|
||||
case "dnd":
|
||||
return UserStatus.DoNotDisturb;
|
||||
case "invisible":
|
||||
return UserStatus.Invisible; //Should never happen
|
||||
case "offline":
|
||||
return UserStatus.Offline;
|
||||
default:
|
||||
@@ -36,6 +40,12 @@ namespace Discord.Net.Converters
|
||||
case UserStatus.Idle:
|
||||
writer.WriteValue("idle");
|
||||
break;
|
||||
case UserStatus.DoNotDisturb:
|
||||
writer.WriteValue("dnd");
|
||||
break;
|
||||
case UserStatus.Invisible:
|
||||
writer.WriteValue("invisible");
|
||||
break;
|
||||
case UserStatus.Offline:
|
||||
writer.WriteValue("offline");
|
||||
break;
|
||||
|
||||
6
src/Discord.Net.Rest/AssemblyInfo.cs
Normal file
6
src/Discord.Net.Rest/AssemblyInfo.cs
Normal 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
src/Discord.Net.Rest/ClientHelper.cs
Normal file
118
src/Discord.Net.Rest/ClientHelper.cs
Normal 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
src/Discord.Net.Rest/DiscordClient.cs
Normal file
162
src/Discord.Net.Rest/DiscordClient.cs
Normal 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(); }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using Discord.Logging;
|
||||
|
||||
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(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)
|
||||
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue());
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true)
|
||||
protected override async Task OnLoginAsync(TokenType tokenType, string token)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternalAsync(tokenType, token).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false);
|
||||
base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser);
|
||||
}
|
||||
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()
|
||||
{
|
||||
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() => Task.CompletedTask;
|
||||
|
||||
/// <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 />
|
||||
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 />
|
||||
public virtual async Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync()
|
||||
{
|
||||
var models = await ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false);
|
||||
return models.Select(x => RestDMChannel.Create(this, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <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<IPrivateChannel>> GetPrivateChannelsAsync()
|
||||
=> ClientHelper.GetPrivateChannelsAsync(this);
|
||||
|
||||
/// <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<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
|
||||
=> ClientHelper.GetConnectionsAsync(this);
|
||||
|
||||
/// <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;
|
||||
}
|
||||
/// <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;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync()
|
||||
{
|
||||
var models = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false);
|
||||
return models.Select(x => RestUserGuild.Create(this, x)).ToImmutableArray();
|
||||
}
|
||||
/// <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();
|
||||
}
|
||||
/// <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<RestInvite> GetInviteAsync(string inviteId)
|
||||
=> ClientHelper.GetInviteAsync(this, inviteId);
|
||||
|
||||
/// <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<RestGuild> GetGuildAsync(ulong id)
|
||||
=> ClientHelper.GetGuildAsync(this, id);
|
||||
/// <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<RestGuildEmbed?> GetGuildEmbedAsync(ulong id)
|
||||
=> ClientHelper.GetGuildEmbedAsync(this, id);
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync()
|
||||
=> ClientHelper.GetGuildSummariesAsync(this);
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync()
|
||||
=> ClientHelper.GetGuildsAsync(this);
|
||||
/// <inheritdoc />
|
||||
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
|
||||
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon);
|
||||
|
||||
/// <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();
|
||||
}
|
||||
public Task<RestUser> GetUserAsync(ulong id)
|
||||
=> ClientHelper.GetUserAsync(this, id);
|
||||
/// <inheritdoc />
|
||||
public Task<RestUser> GetUserAsync(string username, string discriminator)
|
||||
=> ClientHelper.GetUserAsync(this, username, discriminator);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync()
|
||||
{
|
||||
var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false);
|
||||
return models.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableArray();
|
||||
}
|
||||
public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync()
|
||||
=> ClientHelper.GetVoiceRegionsAsync(this);
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
/// <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
|
||||
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()
|
||||
=> await GetConnectionsAsync().ConfigureAwait(false);
|
||||
|
||||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId)
|
||||
=> await GetInviteAsync(inviteId).ConfigureAwait(false);
|
||||
|
||||
async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id)
|
||||
=> await GetGuildAsync(id).ConfigureAwait(false);
|
||||
async Task<IReadOnlyCollection<IUserGuild>> IDiscordClient.GetGuildSummariesAsync()
|
||||
=> await GetGuildSummariesAsync().ConfigureAwait(false);
|
||||
async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync()
|
||||
=> await GetGuildsAsync().ConfigureAwait(false);
|
||||
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon)
|
||||
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false);
|
||||
|
||||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id)
|
||||
=> await GetUserAsync(id).ConfigureAwait(false);
|
||||
async Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator)
|
||||
=> 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()
|
||||
=> await GetVoiceRegionsAsync().ConfigureAwait(false);
|
||||
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id)
|
||||
=> await GetVoiceRegionAsync(id).ConfigureAwait(false);
|
||||
|
||||
Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); }
|
||||
Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,33 +12,33 @@ namespace Discord.Rest
|
||||
internal static class ChannelHelper
|
||||
{
|
||||
//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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
public static async Task ModifyAsync(IGuildChannel channel, DiscordRestClient client,
|
||||
public static async Task ModifyAsync(IGuildChannel channel, DiscordClient client,
|
||||
Action<ModifyGuildChannelParams> func)
|
||||
{
|
||||
var args = new ModifyGuildChannelParams();
|
||||
func(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)
|
||||
{
|
||||
var args = new ModifyTextChannelParams();
|
||||
func(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)
|
||||
{
|
||||
var args = new ModifyVoiceChannelParams();
|
||||
@@ -47,12 +47,12 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//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);
|
||||
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)
|
||||
{
|
||||
var args = new CreateChannelInviteParams { IsTemporary = isTemporary };
|
||||
@@ -65,13 +65,13 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//Messages
|
||||
public static async Task<RestMessage> GetMessageAsync(IChannel channel, DiscordRestClient client,
|
||||
public static async Task<RestMessage> GetMessageAsync(IChannel channel, DiscordClient client,
|
||||
ulong id)
|
||||
{
|
||||
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false);
|
||||
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)
|
||||
{
|
||||
//TODO: Test this with Around direction
|
||||
@@ -102,13 +102,13 @@ namespace Discord.Rest
|
||||
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);
|
||||
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)
|
||||
{
|
||||
var args = new CreateMessageParams(text) { IsTTS = isTTS };
|
||||
@@ -116,14 +116,14 @@ namespace Discord.Rest
|
||||
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 filename = Path.GetFileName(filePath);
|
||||
using (var file = File.OpenRead(filePath))
|
||||
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)
|
||||
{
|
||||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
|
||||
@@ -131,7 +131,7 @@ namespace Discord.Rest
|
||||
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)
|
||||
{
|
||||
var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray());
|
||||
@@ -139,31 +139,31 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//Permission Overwrites
|
||||
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client,
|
||||
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client,
|
||||
IUser user, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue);
|
||||
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)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Users
|
||||
public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, DiscordRestClient client,
|
||||
public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, DiscordClient client,
|
||||
ulong id)
|
||||
{
|
||||
var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id);
|
||||
@@ -175,7 +175,7 @@ namespace Discord.Rest
|
||||
|
||||
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)
|
||||
{
|
||||
return new PagedAsyncEnumerable<RestGuildUser>(
|
||||
@@ -203,7 +203,7 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//Typing
|
||||
public static IDisposable EnterTypingState(IChannel channel, DiscordRestClient client)
|
||||
public static IDisposable EnterTypingState(IChannel channel, DiscordClient client)
|
||||
{
|
||||
throw new NotImplementedException(); //TODO: Impl
|
||||
}
|
||||
|
||||
48
src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
Normal file
48
src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -10,28 +10,31 @@ using Model = Discord.API.Channel;
|
||||
namespace Discord.Rest
|
||||
{
|
||||
[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)
|
||||
{
|
||||
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);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
Recipient.Update(model.Recipients.Value[0]);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync()
|
||||
public override async Task UpdateAsync()
|
||||
=> Update(await ChannelHelper.GetAsync(this, Discord));
|
||||
public Task CloseAsync()
|
||||
=> ChannelHelper.DeleteAsync(this, Discord);
|
||||
@@ -41,7 +44,7 @@ namespace Discord.Rest
|
||||
if (id == Recipient.Id)
|
||||
return Recipient;
|
||||
else if (id == Discord.CurrentUser.Id)
|
||||
return Discord.CurrentUser;
|
||||
return CurrentUser;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using Model = Discord.API.Channel;
|
||||
namespace Discord.Rest
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RestGroupChannel : RestEntity<ulong>, IGroupChannel, IUpdateable
|
||||
public class RestGroupChannel : RestChannel, IGroupChannel, IUpdateable
|
||||
{
|
||||
private string _iconId;
|
||||
private ImmutableDictionary<ulong, RestGroupUser> _users;
|
||||
@@ -21,17 +21,17 @@ namespace Discord.Rest
|
||||
public IReadOnlyCollection<RestGroupUser> Recipients
|
||||
=> _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)
|
||||
{
|
||||
}
|
||||
internal static RestGroupChannel Create(DiscordRestClient discord, Model model)
|
||||
internal new static RestGroupChannel Create(DiscordClient discord, Model model)
|
||||
{
|
||||
var entity = new RestGroupChannel(discord, model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
if (model.Name.IsSpecified)
|
||||
Name = model.Name.Value;
|
||||
@@ -49,7 +49,7 @@ namespace Discord.Rest
|
||||
_users = users.ToImmutable();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync()
|
||||
public override async Task UpdateAsync()
|
||||
=> Update(await ChannelHelper.GetAsync(this, Discord));
|
||||
public Task LeaveAsync()
|
||||
=> ChannelHelper.DeleteAsync(this, Discord);
|
||||
|
||||
@@ -10,7 +10,7 @@ using Model = Discord.API.Channel;
|
||||
namespace Discord.Rest
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public abstract class RestGuildChannel : RestEntity<ulong>, IGuildChannel, IUpdateable
|
||||
public abstract class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable
|
||||
{
|
||||
private ImmutableArray<Overwrite> _overwrites;
|
||||
|
||||
@@ -21,12 +21,12 @@ namespace Discord.Rest
|
||||
public string Name { 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)
|
||||
{
|
||||
GuildId = guildId;
|
||||
}
|
||||
internal static RestGuildChannel Create(DiscordRestClient discord, Model model)
|
||||
internal new static RestGuildChannel Create(DiscordClient discord, Model model)
|
||||
{
|
||||
switch (model.Type)
|
||||
{
|
||||
@@ -38,7 +38,7 @@ namespace Discord.Rest
|
||||
throw new InvalidOperationException("Unknown guild channel type");
|
||||
}
|
||||
}
|
||||
internal virtual void Update(Model model)
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
Name = model.Name.Value;
|
||||
Position = model.Position.Value;
|
||||
@@ -50,7 +50,7 @@ namespace Discord.Rest
|
||||
_overwrites = newOverwrites.ToImmutable();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync()
|
||||
public override async Task UpdateAsync()
|
||||
=> Update(await ChannelHelper.GetAsync(this, Discord));
|
||||
public Task ModifyAsync(Action<ModifyGuildChannelParams> func)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func);
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace Discord.Rest
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace Discord.Rest
|
||||
public int Bitrate { 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)
|
||||
{
|
||||
}
|
||||
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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Discord.Rest
|
||||
internal static class GuildHelper
|
||||
{
|
||||
//General
|
||||
public static async Task<Model> ModifyAsync(IGuild guild, DiscordRestClient client,
|
||||
public static async Task<Model> ModifyAsync(IGuild guild, DiscordClient client,
|
||||
Action<ModifyGuildParams> 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);
|
||||
}
|
||||
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, DiscordRestClient client,
|
||||
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, DiscordClient client,
|
||||
Action<ModifyGuildEmbedParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -37,46 +37,46 @@ namespace Discord.Rest
|
||||
func(args);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
//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);
|
||||
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)
|
||||
{
|
||||
var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays };
|
||||
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)
|
||||
{
|
||||
await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId);
|
||||
}
|
||||
|
||||
//Channels
|
||||
public static async Task<RestGuildChannel> GetChannelAsync(IGuild guild, DiscordRestClient client,
|
||||
public static async Task<RestGuildChannel> GetChannelAsync(IGuild guild, DiscordClient client,
|
||||
ulong id)
|
||||
{
|
||||
var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false);
|
||||
@@ -84,12 +84,12 @@ namespace Discord.Rest
|
||||
return RestGuildChannel.Create(client, model);
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
@@ -109,12 +109,12 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//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);
|
||||
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)
|
||||
{
|
||||
var args = new CreateGuildIntegrationParams(id, type);
|
||||
@@ -123,14 +123,14 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//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);
|
||||
return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
@@ -150,7 +150,7 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
//Users
|
||||
public static async Task<RestGuildUser> GetUserAsync(IGuild guild, DiscordRestClient client,
|
||||
public static async Task<RestGuildUser> GetUserAsync(IGuild guild, DiscordClient client,
|
||||
ulong id)
|
||||
{
|
||||
var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false);
|
||||
@@ -158,17 +158,17 @@ namespace Discord.Rest
|
||||
return RestGuildUser.Create(client, model);
|
||||
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);
|
||||
}
|
||||
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 models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false);
|
||||
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)
|
||||
{
|
||||
var args = new GuildPruneParams(days);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Discord.Rest
|
||||
User = user;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ namespace Discord.Rest
|
||||
public IReadOnlyCollection<Emoji> Emojis => _emojis;
|
||||
public IReadOnlyCollection<string> Features => _features;
|
||||
|
||||
internal RestGuild(DiscordRestClient client, ulong id)
|
||||
internal RestGuild(DiscordClient client, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -25,11 +25,11 @@ namespace Discord.Rest
|
||||
|
||||
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks);
|
||||
|
||||
internal RestGuildIntegration(DiscordRestClient discord, ulong id)
|
||||
internal RestGuildIntegration(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace Discord.Rest
|
||||
|
||||
public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId);
|
||||
|
||||
internal RestUserGuild(DiscordRestClient discord, ulong id)
|
||||
internal RestUserGuild(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -13,11 +13,11 @@ namespace Discord
|
||||
public string SampleHostname { get; private set; }
|
||||
public int SamplePort { get; private set; }
|
||||
|
||||
internal RestVoiceRegion(DiscordRestClient client, string id)
|
||||
internal RestVoiceRegion(DiscordClient client, string 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -5,15 +5,15 @@ namespace Discord.Rest
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace Discord.Rest
|
||||
public string Code => Id;
|
||||
public string Url => $"{DiscordConfig.InviteUrl}/{Code}";
|
||||
|
||||
internal RestInvite(DiscordRestClient discord, string id)
|
||||
internal RestInvite(DiscordClient discord, string 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Discord.Rest
|
||||
|
||||
public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks);
|
||||
|
||||
internal RestInviteMetadata(DiscordRestClient discord, string id)
|
||||
internal RestInviteMetadata(DiscordClient discord, string 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -6,26 +6,26 @@ namespace Discord.Rest
|
||||
{
|
||||
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);
|
||||
}
|
||||
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();
|
||||
func(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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
//TODO: Rename to Attachment?
|
||||
public class RestAttachment : IAttachment
|
||||
{
|
||||
public ulong Id { get; }
|
||||
|
||||
@@ -29,12 +29,12 @@ namespace Discord.Rest
|
||||
|
||||
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)
|
||||
{
|
||||
ChannelId = channelId;
|
||||
}
|
||||
internal static RestMessage Create(DiscordRestClient discord, Model model)
|
||||
internal static RestMessage Create(DiscordClient discord, Model model)
|
||||
{
|
||||
if (model.Type == MessageType.Default)
|
||||
return RestUserMessage.Create(discord, model);
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace Discord.Rest
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -30,11 +30,11 @@ namespace Discord.Rest
|
||||
public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles;
|
||||
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)
|
||||
{
|
||||
}
|
||||
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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace Discord.Rest
|
||||
|
||||
public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId);
|
||||
|
||||
internal RestApplication(DiscordRestClient discord, ulong id)
|
||||
internal RestApplication(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -5,10 +5,10 @@ namespace Discord.Rest
|
||||
public abstract class RestEntity<T> : IEntity<T>
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
public DiscordClient Discord { get; }
|
||||
public T Id { get; }
|
||||
public DiscordRestClient Discord { get; }
|
||||
|
||||
public RestEntity(DiscordRestClient discord, T id)
|
||||
internal RestEntity(DiscordClient discord, T id)
|
||||
{
|
||||
Discord = discord;
|
||||
Id = id;
|
||||
|
||||
@@ -21,11 +21,11 @@ namespace Discord.Rest
|
||||
public bool IsEveryone => Id == Guild.Id;
|
||||
public string Mention => MentionUtils.MentionRole(Id);
|
||||
|
||||
internal RestRole(DiscordRestClient discord, ulong id)
|
||||
internal RestRole(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace Discord.Rest
|
||||
internal static class RoleHelper
|
||||
{
|
||||
//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);
|
||||
}
|
||||
public static async Task ModifyAsync(IRole role, DiscordRestClient client,
|
||||
public static async Task ModifyAsync(IRole role, DiscordClient client,
|
||||
Action<ModifyGuildRoleParams> func)
|
||||
{
|
||||
var args = new ModifyGuildRoleParams();
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace Discord.Rest
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RestGroupUser : RestUser, IGroupUser
|
||||
{
|
||||
internal RestGroupUser(DiscordRestClient discord, ulong id)
|
||||
internal RestGroupUser(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -21,11 +21,11 @@ namespace Discord.Rest
|
||||
|
||||
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
|
||||
|
||||
internal RestGuildUser(DiscordRestClient discord, ulong id)
|
||||
internal RestGuildUser(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -13,11 +13,11 @@ namespace Discord.Rest
|
||||
public bool IsVerified { get; private set; }
|
||||
public bool IsMfaEnabled { get; private set; }
|
||||
|
||||
internal RestSelfUser(DiscordRestClient discord, ulong id)
|
||||
internal RestSelfUser(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Discord.Rest
|
||||
public virtual Game? Game => null;
|
||||
public virtual UserStatus Status => UserStatus.Unknown;
|
||||
|
||||
internal RestUser(DiscordRestClient discord, ulong id)
|
||||
internal RestUser(DiscordClient discord, ulong 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);
|
||||
entity.Update(model);
|
||||
|
||||
@@ -8,22 +8,22 @@ namespace Discord.Rest
|
||||
{
|
||||
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);
|
||||
}
|
||||
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();
|
||||
if (model.Id != user.Id)
|
||||
throw new InvalidOperationException("Unable to update this object using a different token.");
|
||||
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);
|
||||
}
|
||||
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)
|
||||
throw new InvalidOperationException("Unable to modify this object using a different token.");
|
||||
@@ -32,19 +32,19 @@ namespace Discord.Rest
|
||||
func(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();
|
||||
func(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);
|
||||
}
|
||||
|
||||
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);
|
||||
return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args));
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
{
|
||||
"version": "1.0.0-beta2-*",
|
||||
|
||||
"buildOptions": {
|
||||
"compile": {
|
||||
"include": [ "../Discord.Net.Utils/**.cs" ]
|
||||
}
|
||||
},
|
||||
|
||||
"configurations": {
|
||||
"Release": {
|
||||
"buildOptions": {
|
||||
|
||||
@@ -3,6 +3,7 @@ using Discord.API.Rpc;
|
||||
using Discord.Net.Queue;
|
||||
using Discord.Net.Rest;
|
||||
using Discord.Net.WebSockets;
|
||||
using Discord.Rpc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
@@ -66,8 +67,8 @@ namespace Discord.API
|
||||
|
||||
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);
|
||||
_clientId = clientId;
|
||||
|
||||
3
src/Discord.Net.Rpc/AssemblyInfo.cs
Normal file
3
src/Discord.Net.Rpc/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Test")]
|
||||
@@ -11,9 +11,9 @@ using System.Threading.Tasks;
|
||||
|
||||
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 TaskCompletionSource<bool> _connectTask;
|
||||
@@ -58,18 +58,7 @@ namespace Discord.Rpc
|
||||
};
|
||||
}
|
||||
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 />
|
||||
public Task ConnectAsync() => ConnectAsync(false);
|
||||
@@ -371,20 +360,20 @@ namespace Discord.Rpc
|
||||
//Messages
|
||||
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 msg = new RpcMessage(this, data.Message);
|
||||
|
||||
await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);
|
||||
await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/
|
||||
}
|
||||
break;
|
||||
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 msg = new RpcMessage(this, data.Message);
|
||||
|
||||
await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);
|
||||
await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_DELETE":
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
/*public interface IRemoteUserGuild : ISnowflakeEntity
|
||||
{
|
||||
/// <summary> Gets the name of this guild. </summary>
|
||||
string Name { get; }
|
||||
}*/
|
||||
}
|
||||
10
src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs
Normal file
10
src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs
Normal 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
src/Discord.Net.Rpc/Entities/RpcEntity.cs
Normal file
19
src/Discord.Net.Rpc/Entities/RpcEntity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Model = Discord.API.Rpc.RpcUserGuild;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
/*internal class RemoteUserGuild : IRemoteUserGuild, ISnowflakeEntity
|
||||
/*internal class RemoteUserGuild : RpcEntity, IRemoteUserGuild, ISnowflakeEntity
|
||||
{
|
||||
public ulong Id { get; }
|
||||
public DiscordRestClient Discord { get; }
|
||||
@@ -12,7 +12,7 @@ namespace Discord.Rpc
|
||||
|
||||
public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
|
||||
public RemoteUserGuild(DiscordRestClient discord, Model model)
|
||||
internal RemoteUserGuild(DiscordRestClient discord, Model model)
|
||||
{
|
||||
Id = model.Id;
|
||||
Discord = discord;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,35 @@
|
||||
{
|
||||
"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": {
|
||||
"Discord.Net.Core": {
|
||||
"target": "project"
|
||||
},
|
||||
"Discord.Net.Rest": {
|
||||
"target": "project"
|
||||
},
|
||||
"NETStandard.Library": "1.6.0"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"netstandard1.6": {
|
||||
"imports": "dnxcore50"
|
||||
"netstandard1.3": {
|
||||
"imports": [
|
||||
"dotnet5.4",
|
||||
"dnxcore50",
|
||||
"portable-net45+win8"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -4,6 +4,7 @@ using Discord.API.Rest;
|
||||
using Discord.Net.Queue;
|
||||
using Discord.Net.Rest;
|
||||
using Discord.Net.WebSockets;
|
||||
using Discord.WebSocket;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -31,8 +32,8 @@ namespace Discord.API
|
||||
|
||||
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.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+)
|
||||
|
||||
3
src/Discord.Net.WebSocket/AssemblyInfo.cs
Normal file
3
src/Discord.Net.WebSocket/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Test")]
|
||||
@@ -5,6 +5,7 @@ using Discord.WebSocket;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
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 ILogger _audioLogger;
|
||||
#if BENCHMARK
|
||||
private readonly ILogger _benchmarkLogger;
|
||||
#endif
|
||||
private readonly Logger _audioLogger;
|
||||
internal readonly SemaphoreSlim _connectionLock;
|
||||
private readonly JsonSerializer _serializer;
|
||||
|
||||
@@ -63,9 +61,6 @@ namespace Discord.Audio
|
||||
Guild = guild;
|
||||
|
||||
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}");
|
||||
#if BENCHMARK
|
||||
_benchmarkLogger = logManager.CreateLogger("Benchmark");
|
||||
#endif
|
||||
|
||||
_connectionLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
@@ -181,11 +176,11 @@ namespace Discord.Audio
|
||||
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);
|
||||
}
|
||||
public OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null,
|
||||
public Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null,
|
||||
OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000)
|
||||
{
|
||||
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)
|
||||
{
|
||||
#if BENCHMARK
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
#endif
|
||||
try
|
||||
{
|
||||
switch (opCode)
|
||||
@@ -262,15 +252,6 @@ namespace Discord.Audio
|
||||
await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false);
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class OpusDecodeStream : RTPReadStream
|
||||
internal class OpusDecodeStream : RTPReadStream
|
||||
{
|
||||
private readonly byte[] _buffer;
|
||||
private readonly OpusDecoder _decoder;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class OpusEncodeStream : RTPWriteStream
|
||||
internal class OpusEncodeStream : RTPWriteStream
|
||||
{
|
||||
public int SampleRate = 48000;
|
||||
public int Channels = 2;
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.IO;
|
||||
|
||||
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 AudioClient _audioClient;
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.IO;
|
||||
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class RTPWriteStream : Stream
|
||||
internal class RTPWriteStream : Stream
|
||||
{
|
||||
private readonly AudioClient _audioClient;
|
||||
private readonly byte[] _nonce, _secretKey;
|
||||
|
||||
@@ -12,37 +12,37 @@ namespace Discord.WebSocket
|
||||
private const double AverageUsersPerGuild = 47.78; //Source: Googie2149
|
||||
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, SocketGuild> _guilds;
|
||||
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users;
|
||||
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<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels);
|
||||
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.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);
|
||||
|
||||
public DataStore(int guildCount, int dmChannelCount)
|
||||
{
|
||||
double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount;
|
||||
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));
|
||||
_guilds = new ConcurrentDictionary<ulong, SocketGuild>(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier));
|
||||
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(CollectionConcurrencyLevel, (int)(estimatedUsersCount * 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))
|
||||
return channel;
|
||||
return null;
|
||||
@@ -54,7 +54,7 @@ namespace Discord.WebSocket
|
||||
return channel;
|
||||
return null;
|
||||
}
|
||||
internal void AddChannel(ISocketChannel channel)
|
||||
internal void AddChannel(SocketChannel channel)
|
||||
{
|
||||
_channels[channel.Id] = channel;
|
||||
|
||||
@@ -68,9 +68,9 @@ namespace Discord.WebSocket
|
||||
_groupChannels.TryAdd(groupChannel.Id);
|
||||
}
|
||||
}
|
||||
internal ISocketChannel RemoveChannel(ulong id)
|
||||
internal SocketChannel RemoveChannel(ulong id)
|
||||
{
|
||||
ISocketChannel channel;
|
||||
SocketChannel channel;
|
||||
if (_channels.TryRemove(id, out channel))
|
||||
{
|
||||
var dmChannel = channel as SocketDMChannel;
|
||||
|
||||
@@ -4,18 +4,16 @@
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid>
|
||||
<RootNamespace>Discord.Net.WebSocket</RootNamespace>
|
||||
<RootNamespace>Discord.WebSocket</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,4 +1,5 @@
|
||||
using Discord.API.Gateway;
|
||||
using Discord.API;
|
||||
using Discord.API.Gateway;
|
||||
using Discord.Audio;
|
||||
using Discord.Logging;
|
||||
using Discord.Net.Converters;
|
||||
@@ -11,24 +12,22 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
public partial class DiscordSocketClient : DiscordRestClient, IDiscordClient
|
||||
public partial class DiscordSocketClient : DiscordClient, IDiscordClient
|
||||
{
|
||||
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 string _sessionId;
|
||||
private int _lastSeq;
|
||||
private ImmutableDictionary<string, VoiceRegion> _voiceRegions;
|
||||
private ImmutableDictionary<string, RestVoiceRegion> _voiceRegions;
|
||||
private TaskCompletionSource<bool> _connectTask;
|
||||
private CancellationTokenSource _cancelToken, _reconnectCancelToken;
|
||||
private Task _heartbeatTask, _guildDownloadTask, _reconnectTask;
|
||||
@@ -54,16 +53,17 @@ namespace Discord.WebSocket
|
||||
internal int ConnectionTimeout { 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<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();
|
||||
|
||||
/// <summary> Creates a new REST/WebSocket discord client. </summary>
|
||||
public DiscordSocketClient() : this(new DiscordSocketConfig()) { }
|
||||
/// <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;
|
||||
TotalShards = config.TotalShards;
|
||||
@@ -72,14 +72,10 @@ namespace Discord.WebSocket
|
||||
AudioMode = config.AudioMode;
|
||||
WebSocketProvider = config.WebSocketProvider;
|
||||
ConnectionTimeout = config.ConnectionTimeout;
|
||||
|
||||
DataStore = new DataStore(0, 0);
|
||||
|
||||
_nextAudioId = 1;
|
||||
|
||||
_gatewayLogger = LogManager.CreateLogger("Gateway");
|
||||
#if BENCHMARK
|
||||
_benchmarkLogger = _log.CreateLogger("Benchmark");
|
||||
#endif
|
||||
|
||||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||
_serializer.Error += (s, e) =>
|
||||
@@ -107,25 +103,25 @@ namespace Discord.WebSocket
|
||||
GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").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>();
|
||||
}
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (ConnectionState != ConnectionState.Disconnected)
|
||||
await DisconnectInternalAsync(null, false).ConfigureAwait(false);
|
||||
|
||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
||||
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ConnectAsync(bool waitForGuilds = true)
|
||||
{
|
||||
@@ -319,127 +315,55 @@ namespace Discord.WebSocket
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IVoiceRegion> GetVoiceRegionAsync(string id)
|
||||
public Task<RestApplication> GetApplicationInfoAsync()
|
||||
=> ClientHelper.GetApplicationInfoAsync(this);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public SocketGuild GetGuild(ulong id)
|
||||
{
|
||||
VoiceRegion region;
|
||||
return DataStore.GetGuild(id);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
|
||||
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChannel GetChannel(ulong id)
|
||||
{
|
||||
return DataStore.GetChannel(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
|
||||
=> ClientHelper.GetConnectionsAsync(this);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RestInvite> GetInviteAsync(string inviteId)
|
||||
=> ClientHelper.GetInviteAsync(this, inviteId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUser GetUser(ulong id)
|
||||
{
|
||||
return DataStore.GetUser(id);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public IUser GetUser(string username, string discriminator)
|
||||
{
|
||||
return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RestVoiceRegion GetVoiceRegion(string id)
|
||||
{
|
||||
RestVoiceRegion region;
|
||||
if (_voiceRegions.TryGetValue(id, out region))
|
||||
return Task.FromResult<IVoiceRegion>(region);
|
||||
return Task.FromResult<IVoiceRegion>(null);
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
return Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds);
|
||||
}
|
||||
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 />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IUser> GetUserAsync(ulong id)
|
||||
{
|
||||
return Task.FromResult<IUser>(DataStore.GetUser(id));
|
||||
}
|
||||
/// <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());
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override Task<ISelfUser> GetCurrentUserAsync()
|
||||
{
|
||||
return Task.FromResult<ISelfUser>(_currentUser);
|
||||
}
|
||||
internal SocketGlobalUser GetOrAddUser(API.User model, DataStore dataStore)
|
||||
{
|
||||
var user = dataStore.GetOrAddUser(model.Id, _ => new SocketGlobalUser(model));
|
||||
user.AddRef();
|
||||
return user;
|
||||
}
|
||||
internal SocketGlobalUser RemoveUser(ulong id)
|
||||
{
|
||||
return DataStore.RemoveUser(id);
|
||||
return region;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary> Downloads the users list for all large guilds. </summary>
|
||||
public Task DownloadAllUsersAsync()
|
||||
/*public Task DownloadAllUsersAsync()
|
||||
=> 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>
|
||||
public Task DownloadUsersAsync(IEnumerable<IGuild> guilds)
|
||||
@@ -490,20 +414,10 @@ namespace Discord.WebSocket
|
||||
else
|
||||
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)
|
||||
{
|
||||
#if BENCHMARK
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (seq != null)
|
||||
_lastSeq = seq.Value;
|
||||
try
|
||||
@@ -516,7 +430,7 @@ namespace Discord.WebSocket
|
||||
var data = (payload as JToken).ToObject<HelloEvent>(_serializer);
|
||||
|
||||
_heartbeatTime = 0;
|
||||
_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, _clientLogger);
|
||||
_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, LogManager.ClientLogger);
|
||||
}
|
||||
break;
|
||||
case GatewayOpCode.Heartbeat:
|
||||
@@ -574,9 +488,9 @@ namespace Discord.WebSocket
|
||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
|
||||
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;
|
||||
for (int i = 0; i < data.Guilds.Length; i++)
|
||||
/*for (int i = 0; i < data.Guilds.Length; i++)
|
||||
{
|
||||
var model = data.Guilds[i];
|
||||
var guild = AddGuild(model, dataStore);
|
||||
@@ -586,10 +500,10 @@ namespace Discord.WebSocket
|
||||
await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false);
|
||||
}
|
||||
for (int i = 0; i < data.PrivateChannels.Length; i++)
|
||||
AddPrivateChannel(data.PrivateChannels[i], dataStore);
|
||||
AddPrivateChannel(data.PrivateChannels[i], dataStore);*/
|
||||
|
||||
_sessionId = data.SessionId;
|
||||
_currentUser = currentUser;
|
||||
base.CurrentUser = currentUser;
|
||||
_unavailableGuilds = unavailableGuilds;
|
||||
DataStore = dataStore;
|
||||
}
|
||||
@@ -603,7 +517,7 @@ namespace Discord.WebSocket
|
||||
await SyncGuildsAsync().ConfigureAwait(false);
|
||||
|
||||
_lastGuildAvailableTime = Environment.TickCount;
|
||||
_guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger);
|
||||
_guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, LogManager.ClientLogger);
|
||||
|
||||
await _readyEvent.InvokeAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -611,7 +525,7 @@ namespace Discord.WebSocket
|
||||
await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "RESUMED":
|
||||
/*case "RESUMED":
|
||||
{
|
||||
await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false);
|
||||
|
||||
@@ -1366,7 +1280,7 @@ namespace Discord.WebSocket
|
||||
}
|
||||
else
|
||||
{
|
||||
before = new Presence(null, UserStatus.Offline);
|
||||
before = new SocketPresence(null, UserStatus.Offline);
|
||||
user = guild.AddOrUpdateUser(data, DataStore);
|
||||
}
|
||||
|
||||
@@ -1430,7 +1344,7 @@ namespace Discord.WebSocket
|
||||
if (data.GuildId.HasValue)
|
||||
{
|
||||
ISocketUser user;
|
||||
VoiceState before, after;
|
||||
SocketVoiceState before, after;
|
||||
if (data.GuildId != null)
|
||||
{
|
||||
var guild = DataStore.GetGuild(data.GuildId.Value);
|
||||
@@ -1444,7 +1358,7 @@ namespace Discord.WebSocket
|
||||
|
||||
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);
|
||||
if (data.UserId == _currentUser.Id)
|
||||
{
|
||||
@@ -1453,8 +1367,8 @@ namespace Discord.WebSocket
|
||||
}
|
||||
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);
|
||||
@@ -1472,13 +1386,13 @@ namespace Discord.WebSocket
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -1518,7 +1432,7 @@ namespace Discord.WebSocket
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
return;*/
|
||||
|
||||
//Ignored (User only)
|
||||
case "CHANNEL_PINS_ACK":
|
||||
@@ -1550,18 +1464,9 @@ namespace Discord.WebSocket
|
||||
await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false);
|
||||
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
|
||||
{
|
||||
@@ -1601,7 +1506,7 @@ namespace Discord.WebSocket
|
||||
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
|
||||
try
|
||||
@@ -1626,5 +1531,42 @@ namespace Discord.WebSocket
|
||||
if (guildIds.Length > 0)
|
||||
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
src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs
Normal file
45
src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,138 @@
|
||||
using System.Collections.Generic;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
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)
|
||||
_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 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 Task CloseAsync()
|
||||
=> ChannelHelper.DeleteAsync(this, Discord);
|
||||
|
||||
public SocketUser GetUser(ulong id)
|
||||
{
|
||||
var currentUser = Discord.CurrentUser;
|
||||
if (id == Recipient.Id)
|
||||
return Recipient;
|
||||
else if (id == currentUser.Id)
|
||||
return currentUser;
|
||||
else if (id == Discord.CurrentUser.Id)
|
||||
return Discord.CurrentUser as SocketSelfUser;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IMessage> GetMessageAsync(ulong id)
|
||||
public SocketMessage GetMessage(ulong id)
|
||||
=> _messages?.Get(id);
|
||||
public async Task<IMessage> GetMessageAsync(ulong id, bool allowDownload = true)
|
||||
{
|
||||
return await _messages.DownloadAsync(id).ConfigureAwait(false);
|
||||
IMessage msg = _messages?.Get(id);
|
||||
if (msg == null && allowDownload)
|
||||
msg = await ChannelHelper.GetMessageAsync(this, Discord, id);
|
||||
return msg;
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
var msg = SocketMessage.Create(Discord, author, model);
|
||||
_messages.Add(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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
@@ -11,134 +14,124 @@ using VoiceStateModel = Discord.API.VoiceState;
|
||||
|
||||
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)
|
||||
_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 UpdateUsers(UserModel[] models, DataStore dataStore)
|
||||
internal void Update(Model model)
|
||||
{
|
||||
var users = new ConcurrentDictionary<ulong, GroupUser>(1, models.Length);
|
||||
if (model.Name.IsSpecified)
|
||||
Name = model.Name.Value;
|
||||
if (model.Icon.IsSpecified)
|
||||
_iconId = model.Icon.Value;
|
||||
|
||||
if (model.Recipients.IsSpecified)
|
||||
UpdateUsers(model.Recipients.Value);
|
||||
}
|
||||
internal virtual void UpdateUsers(API.User[] models)
|
||||
{
|
||||
var users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, (int)(models.Length * 1.05));
|
||||
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;
|
||||
}
|
||||
internal override void UpdateUsers(UserModel[] models)
|
||||
=> UpdateUsers(models, source, Discord.DataStore);
|
||||
|
||||
public SocketGroupUser AddUser(UserModel model, DataStore dataStore)
|
||||
public async Task UpdateAsync()
|
||||
=> Update(await ChannelHelper.GetAsync(this, Discord));
|
||||
public Task LeaveAsync()
|
||||
=> ChannelHelper.DeleteAsync(this, Discord);
|
||||
|
||||
public SocketGroupUser GetUser(ulong id)
|
||||
{
|
||||
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)
|
||||
{
|
||||
GroupUser user;
|
||||
SocketGroupUser 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;
|
||||
}
|
||||
|
||||
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 SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel;
|
||||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
|
||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages);
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
|
||||
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id);
|
||||
ISocketChannel ISocketChannel.Clone() => Clone();
|
||||
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();
|
||||
|
||||
//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>>(Users).ToAsyncEnumerable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Discord.API.Rest;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -7,89 +8,59 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
[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 IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites;
|
||||
|
||||
public ulong GuildId { get; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
public int Position { get; private set; }
|
||||
|
||||
public Guild Guild { get; private set; }
|
||||
|
||||
public override DiscordRestClient Discord => Guild.Discord;
|
||||
|
||||
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;
|
||||
Position = model.Position.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++)
|
||||
newOverwrites.Add(new Overwrite(overwrites[i]));
|
||||
_overwrites = newOverwrites;
|
||||
_overwrites = newOverwrites.ToImmutable();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
for (int i = 0; i < _overwrites.Count; i++)
|
||||
for (int i = 0; i < _overwrites.Length; i++)
|
||||
{
|
||||
if (_overwrites[i].TargetId == user.Id)
|
||||
return _overwrites[i].Permissions;
|
||||
@@ -98,60 +69,91 @@ namespace Discord.Rest
|
||||
}
|
||||
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)
|
||||
return _overwrites[i].Permissions;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 }));
|
||||
}
|
||||
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)
|
||||
{
|
||||
_overwrites.RemoveAt(i);
|
||||
_overwrites = _overwrites.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
_overwrites.RemoveAt(i);
|
||||
_overwrites = _overwrites.RemoveAt(i);
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using Discord.Rest;
|
||||
using Discord.API.Rest;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
@@ -8,81 +12,105 @@ using Model = Discord.API.Channel;
|
||||
|
||||
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)
|
||||
_messages = new MessageCache(Discord, this);
|
||||
else
|
||||
_messages = new MessageManager(Discord, this);
|
||||
}
|
||||
internal new static SocketTextChannel Create(DiscordSocketClient discord, Model model)
|
||||
{
|
||||
var entity = new SocketTextChannel(discord, model.Id, model.GuildId.Value);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
|
||||
Topic = model.Topic.Value;
|
||||
}
|
||||
|
||||
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;
|
||||
public Task ModifyAsync(Action<ModifyTextChannelParams> func)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
ulong perms = Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue);
|
||||
if (Permissions.GetValue(perms, ChannelPermission.ReadMessages))
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public Task<RestGuildUser> GetUserAsync(ulong id)
|
||||
=> ChannelHelper.GetUserAsync(this, Discord, id);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync()
|
||||
=> ChannelHelper.GetUsersAsync(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 = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
|
||||
}
|
||||
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 ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
|
||||
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)
|
||||
{
|
||||
return _messages.Create(author, model);
|
||||
}
|
||||
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
var msg = _messages.Create(author, model);
|
||||
var msg = SocketMessage.Create(Discord, author, model);
|
||||
_messages.Add(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);
|
||||
}
|
||||
|
||||
public SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel;
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"@{Name} ({Id}, Text)";
|
||||
|
||||
IReadOnlyCollection<ISocketUser> ISocketMessageChannel.Users => Members;
|
||||
//IGuildChannel
|
||||
async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id)
|
||||
=> await GetUserAsync(id);
|
||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync()
|
||||
=> GetUsersAsync();
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
|
||||
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id, skipCheck);
|
||||
ISocketChannel ISocketChannel.Clone() => Clone();
|
||||
//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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,50 @@
|
||||
using Discord.Audio;
|
||||
using Discord.API.Rest;
|
||||
using Discord.Audio;
|
||||
using Discord.Rest;
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
|
||||
Bitrate = model.Bitrate.Value;
|
||||
UserLimit = model.UserLimit.Value;
|
||||
}
|
||||
|
||||
public override async Task<IAudioClient> ConnectAsync()
|
||||
{
|
||||
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);
|
||||
}
|
||||
public Task ModifyAsync(Action<ModifyVoiceChannelParams> func)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func);
|
||||
|
||||
public SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;
|
||||
//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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Discord.Audio;
|
||||
using Discord.API.Rest;
|
||||
using Discord.Audio;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -19,402 +20,204 @@ using VoiceStateModel = Discord.API.VoiceState;
|
||||
|
||||
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;
|
||||
|
||||
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 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 bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
|
||||
public bool IsSynced => _syncPromise.Task.IsCompleted;
|
||||
public Task SyncPromise => _syncPromise.Task;
|
||||
public Task DownloaderPromise => _downloaderPromise.Task;
|
||||
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 new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public SocketGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id);
|
||||
public IReadOnlyCollection<ISocketGuildChannel> Channels
|
||||
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)
|
||||
{
|
||||
get
|
||||
}
|
||||
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 channels = _channels;
|
||||
var store = Discord.DataStore;
|
||||
return channels.Select(x => store.GetChannel(x) as ISocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels);
|
||||
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();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
else
|
||||
_emojis = ImmutableArray.Create<Emoji>();
|
||||
|
||||
public void Update(ExtendedModel model, DataStore dataStore)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
if (model.Features != null)
|
||||
_features = model.Features.ToImmutableArray();
|
||||
else
|
||||
_features = ImmutableArray.Create<string>();
|
||||
|
||||
_available = !(model.Unavailable ?? false);
|
||||
if (!_available)
|
||||
var roles = ImmutableDictionary.CreateBuilder<ulong, RestRole>();
|
||||
if (model.Roles != null)
|
||||
{
|
||||
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;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
_members = members;
|
||||
_roles = roles.ToImmutable();
|
||||
}
|
||||
|
||||
public void Update(EmojiUpdateModel model)
|
||||
//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)
|
||||
{
|
||||
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();
|
||||
RestRole value;
|
||||
if (_roles.TryGetValue(id, out value))
|
||||
return value;
|
||||
return null;
|
||||
}
|
||||
|
||||
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)
|
||||
public async Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false)
|
||||
{
|
||||
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;
|
||||
var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted);
|
||||
_roles = _roles.Add(role.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;
|
||||
//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);
|
||||
|
||||
SocketGuildUser member;
|
||||
if (members.TryGetValue(model.User.Id, out member))
|
||||
member.Update(model, UpdateSource.WebSocket);
|
||||
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;
|
||||
public Task<int> PruneUsersAsync(int days = 30, bool simulate = false)
|
||||
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate);
|
||||
|
||||
SocketGuildUser member;
|
||||
if (members.TryGetValue(model.User.Id, out member))
|
||||
member.Update(model, UpdateSource.WebSocket);
|
||||
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);
|
||||
return null;
|
||||
}
|
||||
public override async Task DownloadUsersAsync()
|
||||
{
|
||||
await Discord.DownloadUsersAsync(new [] { this });
|
||||
}
|
||||
public void CompleteDownloadMembers()
|
||||
{
|
||||
_downloaderPromise.TrySetResultAsync(true);
|
||||
}
|
||||
//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;
|
||||
|
||||
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;
|
||||
}
|
||||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync()
|
||||
=> await GetBansAsync();
|
||||
|
||||
public async Task<IAudioClient> ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute)
|
||||
{
|
||||
try
|
||||
{
|
||||
TaskCompletionSource<AudioClient> promise;
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync()
|
||||
=> await GetIntegrationsAsync();
|
||||
async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type)
|
||||
=> await CreateIntegrationAsync(id, type);
|
||||
|
||||
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;
|
||||
async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync()
|
||||
=> await GetInvitesAsync();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
IRole IGuild.GetRole(ulong id)
|
||||
=> GetRole(id);
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id;
|
||||
GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions;
|
||||
IAudioClient IGuild.AudioClient => AudioClient;
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Discord.Rest;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -7,53 +9,52 @@ using System.Threading.Tasks;
|
||||
|
||||
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 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;
|
||||
_messages = new ConcurrentDictionary<ulong, ISocketMessage>(1, (int)(_size * 1.05));
|
||||
_messages = new ConcurrentDictionary<ulong, SocketMessage>(1, (int)(_size * 1.05));
|
||||
_orderedMessages = new ConcurrentQueue<ulong>();
|
||||
}
|
||||
|
||||
public override void Add(ISocketMessage message)
|
||||
public void Add(SocketMessage message)
|
||||
{
|
||||
if (_messages.TryAdd(message.Id, message))
|
||||
{
|
||||
_orderedMessages.Enqueue(message.Id);
|
||||
|
||||
ulong msgId;
|
||||
ISocketMessage msg;
|
||||
SocketMessage msg;
|
||||
while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId))
|
||||
_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);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public override ISocketMessage Get(ulong id)
|
||||
public SocketMessage Get(ulong id)
|
||||
{
|
||||
ISocketMessage result;
|
||||
SocketMessage result;
|
||||
if (_messages.TryGetValue(id, out result))
|
||||
return result;
|
||||
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) return ImmutableArray<ISocketMessage>.Empty;
|
||||
if (limit == 0) return ImmutableArray<SocketMessage>.Empty;
|
||||
|
||||
IEnumerable<ulong> cachedMessageIds;
|
||||
if (fromMessageId == null)
|
||||
@@ -67,7 +68,7 @@ namespace Discord.WebSocket
|
||||
.Take(limit)
|
||||
.Select(x =>
|
||||
{
|
||||
ISocketMessage msg;
|
||||
SocketMessage msg;
|
||||
if (_messages.TryGetValue(x, out msg))
|
||||
return msg;
|
||||
return null;
|
||||
@@ -75,13 +76,5 @@ namespace Discord.WebSocket
|
||||
.Where(x => x != null)
|
||||
.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
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
Normal file
64
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,25 @@ using Model = Discord.API.Message;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
|
||||
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 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
src/Discord.Net.WebSocket/Entities/SocketEntity.cs
Normal file
19
src/Discord.Net.WebSocket/Entities/SocketEntity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal interface ISocketUser : IUser, IEntity<ulong>
|
||||
{
|
||||
SocketGlobalUser User { get; }
|
||||
|
||||
ISocketUser Clone();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
using Discord.Rest;
|
||||
using System.Diagnostics;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 PresenceModel = Discord.API.Presence;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketGuildUser : GuildUser, ISocketUser, IVoiceState
|
||||
internal class SocketGuildUser : SocketUser, IGuildUser
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
private long? _joinedAtTicks;
|
||||
private ImmutableArray<ulong> _roleIds;
|
||||
|
||||
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 string Nickname { get; private set; }
|
||||
public ulong GuildId { get; private set; }
|
||||
|
||||
public override Game Game => Presence.Game;
|
||||
public override UserStatus Status => Presence.Status;
|
||||
public IReadOnlyCollection<ulong> RoleIds => _roleIds;
|
||||
|
||||
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 DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
|
||||
|
||||
public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, Model model)
|
||||
: base(guild, user, model)
|
||||
{
|
||||
//Presence = new Presence(null, UserStatus.Offline);
|
||||
}
|
||||
public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, PresenceModel model)
|
||||
: base(guild, user, model)
|
||||
internal SocketGuildUser(DiscordSocketClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update(PresenceModel model)
|
||||
internal static SocketGuildUser Create(DiscordSocketClient discord, Model model)
|
||||
{
|
||||
base.Update(model, source);
|
||||
|
||||
var game = model.Game != null ? new Game(model.Game) : null;
|
||||
//Presence = new Presence(game, model.Status);
|
||||
|
||||
User.Update(model, source);
|
||||
var entity = new SocketGuildUser(discord, model.User.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
_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();
|
||||
}
|
||||
|
||||
IVoiceChannel IVoiceState.VoiceChannel => VoiceState?.VoiceChannel;
|
||||
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);
|
||||
|
||||
public SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser;
|
||||
ISocketUser ISocketUser.Clone() => Clone();
|
||||
public ChannelPermissions GetPermissions(IGuildChannel channel)
|
||||
{
|
||||
throw new NotImplementedException(); //TODO: Impl
|
||||
}
|
||||
|
||||
//IGuildUser
|
||||
IReadOnlyCollection<ulong> IGuildUser.RoleIds => RoleIds;
|
||||
|
||||
//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 have changed in this diff Show More
Reference in New Issue
Block a user