| @@ -15,8 +15,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Dis | |||||
| EndProject | EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | ||||
| EndProject | EndProject | ||||
| Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Discord.Net.Utils\Discord.Net.Utils.shproj", "{2B75119C-9893-4AAA-8D38-6176EEB09060}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" | ||||
| EndProject | EndProject | ||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" | ||||
| @@ -24,9 +22,6 @@ EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" | ||||
| EndProject | EndProject | ||||
| Global | Global | ||||
| GlobalSection(SharedMSBuildProjectFiles) = preSolution | |||||
| src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13 | |||||
| EndGlobalSection | |||||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
| Release|Any CPU = Release|Any CPU | Release|Any CPU = Release|Any CPU | ||||
| @@ -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 bool RequireColons { get; } | ||||
| public IReadOnlyList<ulong> RoleIds { get; } | public IReadOnlyList<ulong> RoleIds { get; } | ||||
| public Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds) | |||||
| private Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds) | |||||
| { | { | ||||
| Id = id; | Id = id; | ||||
| Name = name; | Name = name; | ||||
| @@ -22,7 +22,7 @@ namespace Discord | |||||
| RequireColons = requireColons; | RequireColons = requireColons; | ||||
| RoleIds = roleIds; | RoleIds = roleIds; | ||||
| } | } | ||||
| public static Emoji Create(Model model) | |||||
| internal static Emoji Create(Model model) | |||||
| { | { | ||||
| return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); | return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); | ||||
| } | } | ||||
| @@ -7,12 +7,12 @@ namespace Discord | |||||
| public string Name { get; } | public string Name { get; } | ||||
| public string Url { get; } | public string Url { get; } | ||||
| public EmbedProvider(string name, string url) | |||||
| private EmbedProvider(string name, string url) | |||||
| { | { | ||||
| Name = name; | Name = name; | ||||
| Url = url; | Url = url; | ||||
| } | } | ||||
| public static EmbedProvider Create(Model model) | |||||
| internal static EmbedProvider Create(Model model) | |||||
| { | { | ||||
| return new EmbedProvider(model.Name, model.Url); | return new EmbedProvider(model.Name, model.Url); | ||||
| } | } | ||||
| @@ -9,14 +9,14 @@ namespace Discord | |||||
| public int? Height { get; } | public int? Height { get; } | ||||
| public int? Width { get; } | public int? Width { get; } | ||||
| public EmbedThumbnail(string url, string proxyUrl, int? height, int? width) | |||||
| private EmbedThumbnail(string url, string proxyUrl, int? height, int? width) | |||||
| { | { | ||||
| Url = url; | Url = url; | ||||
| ProxyUrl = proxyUrl; | ProxyUrl = proxyUrl; | ||||
| Height = height; | Height = height; | ||||
| Width = width; | Width = width; | ||||
| } | } | ||||
| public static EmbedThumbnail Create(Model model) | |||||
| internal static EmbedThumbnail Create(Model model) | |||||
| { | { | ||||
| return new EmbedThumbnail(model.Url, model.ProxyUrl, | return new EmbedThumbnail(model.Url, model.ProxyUrl, | ||||
| model.Height.IsSpecified ? model.Height.Value : (int?)null, | model.Height.IsSpecified ? model.Height.Value : (int?)null, | ||||
| @@ -16,9 +16,9 @@ namespace Discord | |||||
| StreamUrl = streamUrl; | StreamUrl = streamUrl; | ||||
| StreamType = type; | StreamType = type; | ||||
| } | } | ||||
| public Game(string name) | |||||
| private Game(string name) | |||||
| : this(name, null, StreamType.NotStreaming) { } | : this(name, null, StreamType.NotStreaming) { } | ||||
| public static Game Create(Model model) | |||||
| internal static Game Create(Model model) | |||||
| { | { | ||||
| return new Game(model.Name, | return new Game(model.Name, | ||||
| model.StreamUrl.GetValueOrDefault(null), | model.StreamUrl.GetValueOrDefault(null), | ||||
| @@ -5,6 +5,8 @@ | |||||
| Unknown, | Unknown, | ||||
| Online, | Online, | ||||
| Idle, | Idle, | ||||
| DoNotDisturb, | |||||
| Invisible, | |||||
| Offline | Offline | ||||
| } | } | ||||
| } | } | ||||
| @@ -27,9 +27,7 @@ | |||||
| public static string Sanitize(string text) | public static string Sanitize(string text) | ||||
| { | { | ||||
| foreach (string unsafeChar in SensitiveCharacters) | foreach (string unsafeChar in SensitiveCharacters) | ||||
| { | |||||
| text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | ||||
| } | |||||
| return text; | return text; | ||||
| } | } | ||||
| } | } | ||||
| @@ -26,14 +26,12 @@ namespace Discord | |||||
| Task<IGuild> GetGuildAsync(ulong id); | Task<IGuild> GetGuildAsync(ulong id); | ||||
| Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(); | Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(); | ||||
| Task<IReadOnlyCollection<IUserGuild>> GetGuildSummariesAsync(); | |||||
| Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); | Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); | ||||
| Task<IInvite> GetInviteAsync(string inviteId); | Task<IInvite> GetInviteAsync(string inviteId); | ||||
| Task<IUser> GetUserAsync(ulong id); | Task<IUser> GetUserAsync(ulong id); | ||||
| Task<IUser> GetUserAsync(string username, string discriminator); | Task<IUser> GetUserAsync(string username, string discriminator); | ||||
| Task<IReadOnlyCollection<IUser>> QueryUsersAsync(string query, int limit); | |||||
| Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(); | Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(); | ||||
| Task<IVoiceRegion> GetVoiceRegionAsync(string id); | Task<IVoiceRegion> GetVoiceRegionAsync(string id); | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Logging | namespace Discord.Logging | ||||
| @@ -6,6 +7,7 @@ namespace Discord.Logging | |||||
| internal class LogManager | internal class LogManager | ||||
| { | { | ||||
| public LogSeverity Level { get; } | public LogSeverity Level { get; } | ||||
| public Logger ClientLogger { get; } | |||||
| public event Func<LogMessage, Task> Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } | public event Func<LogMessage, Task> Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<LogMessage, Task>> _messageEvent = new AsyncEvent<Func<LogMessage, Task>>(); | private readonly AsyncEvent<Func<LogMessage, Task>> _messageEvent = new AsyncEvent<Func<LogMessage, Task>>(); | ||||
| @@ -13,6 +15,7 @@ namespace Discord.Logging | |||||
| public LogManager(LogSeverity minSeverity) | public LogManager(LogSeverity minSeverity) | ||||
| { | { | ||||
| Level = minSeverity; | Level = minSeverity; | ||||
| ClientLogger = new Logger(this, "Discord"); | |||||
| } | } | ||||
| public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) | public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) | ||||
| @@ -67,5 +70,21 @@ namespace Discord.Logging | |||||
| => LogAsync(LogSeverity.Debug, source, ex); | => LogAsync(LogSeverity.Debug, source, ex); | ||||
| public Logger CreateLogger(string name) => new Logger(this, name); | public Logger CreateLogger(string name) => new Logger(this, name); | ||||
| public async Task WriteInitialLog() | |||||
| { | |||||
| await ClientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); | |||||
| await ClientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); | |||||
| await ClientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); | |||||
| } | |||||
| private static string ToArchString(Architecture arch) | |||||
| { | |||||
| switch (arch) | |||||
| { | |||||
| case Architecture.X64: return "x64"; | |||||
| case Architecture.X86: return "x86"; | |||||
| default: return arch.ToString(); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -19,6 +19,10 @@ namespace Discord.Net.Converters | |||||
| return UserStatus.Online; | return UserStatus.Online; | ||||
| case "idle": | case "idle": | ||||
| return UserStatus.Idle; | return UserStatus.Idle; | ||||
| case "dnd": | |||||
| return UserStatus.DoNotDisturb; | |||||
| case "invisible": | |||||
| return UserStatus.Invisible; //Should never happen | |||||
| case "offline": | case "offline": | ||||
| return UserStatus.Offline; | return UserStatus.Offline; | ||||
| default: | default: | ||||
| @@ -36,6 +40,12 @@ namespace Discord.Net.Converters | |||||
| case UserStatus.Idle: | case UserStatus.Idle: | ||||
| writer.WriteValue("idle"); | writer.WriteValue("idle"); | ||||
| break; | break; | ||||
| case UserStatus.DoNotDisturb: | |||||
| writer.WriteValue("dnd"); | |||||
| break; | |||||
| case UserStatus.Invisible: | |||||
| writer.WriteValue("invisible"); | |||||
| break; | |||||
| case UserStatus.Offline: | case UserStatus.Offline: | ||||
| writer.WriteValue("offline"); | writer.WriteValue("offline"); | ||||
| break; | break; | ||||
| @@ -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")] | |||||
| @@ -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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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.Generic; | ||||
| using System.Collections.Immutable; | |||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Runtime.InteropServices; | |||||
| using Discord.Logging; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| public class DiscordRestClient : IDiscordClient | |||||
| public class DiscordRestClient : DiscordClient, IDiscordClient | |||||
| { | { | ||||
| private readonly object _eventLock = new object(); | |||||
| public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | |||||
| public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||||
| public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>(); | |||||
| internal readonly Logger _clientLogger, _restLogger, _queueLogger; | |||||
| internal readonly SemaphoreSlim _connectionLock; | |||||
| private bool _isFirstLogSub; | |||||
| internal bool _isDisposed; | |||||
| public API.DiscordRestApiClient ApiClient { get; } | |||||
| internal LogManager LogManager { get; } | |||||
| public LoginState LoginState { get; private set; } | |||||
| public RestSelfUser CurrentUser { get; private set; } | |||||
| /// <summary> Creates a new REST-only discord client. </summary> | |||||
| public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
| public DiscordRestClient(DiscordRestConfig config) : this(config, CreateApiClient(config)) { } | |||||
| /// <summary> Creates a new REST-only discord client. </summary> | |||||
| internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient client) | |||||
| { | |||||
| ApiClient = client; | |||||
| LogManager = new LogManager(config.LogLevel); | |||||
| LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||||
| _clientLogger = LogManager.CreateLogger("Client"); | |||||
| _restLogger = LogManager.CreateLogger("Rest"); | |||||
| _queueLogger = LogManager.CreateLogger("Queue"); | |||||
| _isFirstLogSub = true; | |||||
| public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { } | |||||
| _connectionLock = new SemaphoreSlim(1, 1); | |||||
| ApiClient.RequestQueue.RateLimitTriggered += async (id, bucket, millis) => | |||||
| { | |||||
| await _queueLogger.WarningAsync($"Rate limit triggered (id = \"{id ?? "null"}\")").ConfigureAwait(false); | |||||
| if (bucket == null && id != null) | |||||
| await _queueLogger.WarningAsync($"Unknown rate limit bucket \"{id ?? "null"}\"").ConfigureAwait(false); | |||||
| }; | |||||
| ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | |||||
| } | |||||
| private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | ||||
| => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue()); | => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue()); | ||||
| /// <inheritdoc /> | |||||
| public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | |||||
| { | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await LoginInternalAsync(tokenType, token).ConfigureAwait(false); | |||||
| } | |||||
| finally { _connectionLock.Release(); } | |||||
| } | |||||
| private async Task LoginInternalAsync(TokenType tokenType, string token) | |||||
| { | |||||
| if (_isFirstLogSub) | |||||
| { | |||||
| _isFirstLogSub = false; | |||||
| await WriteInitialLog().ConfigureAwait(false); | |||||
| } | |||||
| if (LoginState != LoginState.LoggedOut) | |||||
| await LogoutInternalAsync().ConfigureAwait(false); | |||||
| LoginState = LoginState.LoggingIn; | |||||
| try | |||||
| { | |||||
| await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); | |||||
| CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); | |||||
| await OnLoginAsync(tokenType, token).ConfigureAwait(false); | |||||
| LoginState = LoginState.LoggedIn; | |||||
| } | |||||
| catch (Exception) | |||||
| { | |||||
| await LogoutInternalAsync().ConfigureAwait(false); | |||||
| throw; | |||||
| } | |||||
| await _loggedInEvent.InvokeAsync().ConfigureAwait(false); | |||||
| } | |||||
| protected virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.CompletedTask; | |||||
| /// <inheritdoc /> | |||||
| public async Task LogoutAsync() | |||||
| protected override async Task OnLoginAsync(TokenType tokenType, string token) | |||||
| { | { | ||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await LogoutInternalAsync().ConfigureAwait(false); | |||||
| } | |||||
| finally { _connectionLock.Release(); } | |||||
| await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); | |||||
| base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); | |||||
| } | } | ||||
| private async Task LogoutInternalAsync() | |||||
| { | |||||
| if (LoginState == LoginState.LoggedOut) return; | |||||
| LoginState = LoginState.LoggingOut; | |||||
| await ApiClient.LogoutAsync().ConfigureAwait(false); | |||||
| await OnLogoutAsync().ConfigureAwait(false); | |||||
| CurrentUser = null; | |||||
| LoginState = LoginState.LoggedOut; | |||||
| await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); | |||||
| } | |||||
| protected virtual Task OnLogoutAsync() => Task.CompletedTask; | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IApplication> GetApplicationInfoAsync() | |||||
| { | |||||
| var model = await ApiClient.GetMyApplicationAsync().ConfigureAwait(false); | |||||
| return RestApplication.Create(this, model); | |||||
| } | |||||
| public Task<RestApplication> GetApplicationInfoAsync() | |||||
| => ClientHelper.GetApplicationInfoAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<IChannel> GetChannelAsync(ulong id) | |||||
| { | |||||
| var model = await ApiClient.GetChannelAsync(id).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| { | |||||
| switch (model.Type) | |||||
| { | |||||
| case ChannelType.Text: | |||||
| return RestTextChannel.Create(this, model); | |||||
| case ChannelType.Voice: | |||||
| return RestVoiceChannel.Create(this, model); | |||||
| case ChannelType.DM: | |||||
| return RestDMChannel.Create(this, model); | |||||
| case ChannelType.Group: | |||||
| return RestGroupChannel.Create(this, model); | |||||
| default: | |||||
| throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| public Task<RestChannel> GetChannelAsync(ulong id) | |||||
| => ClientHelper.GetChannelAsync(this, id); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync() | |||||
| { | |||||
| var models = await ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false); | |||||
| return models.Select(x => RestDMChannel.Create(this, x)).ToImmutableArray(); | |||||
| } | |||||
| public Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync() | |||||
| => ClientHelper.GetPrivateChannelsAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync() | |||||
| { | |||||
| var models = await ApiClient.GetMyConnectionsAsync().ConfigureAwait(false); | |||||
| return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); | |||||
| } | |||||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync() | |||||
| => ClientHelper.GetConnectionsAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<RestInvite> GetInviteAsync(string inviteId) | |||||
| { | |||||
| var model = await ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return RestInvite.Create(this, model); | |||||
| return null; | |||||
| } | |||||
| public Task<RestInvite> GetInviteAsync(string inviteId) | |||||
| => ClientHelper.GetInviteAsync(this, inviteId); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<RestGuild> GetGuildAsync(ulong id) | |||||
| { | |||||
| var model = await ApiClient.GetGuildAsync(id).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return RestGuild.Create(this, model); | |||||
| return null; | |||||
| } | |||||
| public Task<RestGuild> GetGuildAsync(ulong id) | |||||
| => ClientHelper.GetGuildAsync(this, id); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id) | |||||
| { | |||||
| var model = await ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return RestGuildEmbed.Create(model); | |||||
| return null; | |||||
| } | |||||
| public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id) | |||||
| => ClientHelper.GetGuildEmbedAsync(this, id); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync() | |||||
| { | |||||
| var models = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false); | |||||
| return models.Select(x => RestUserGuild.Create(this, x)).ToImmutableArray(); | |||||
| } | |||||
| public Task<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync() | |||||
| => ClientHelper.GetGuildSummariesAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync() | |||||
| { | |||||
| var summaryModels = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false); | |||||
| var guilds = ImmutableArray.CreateBuilder<RestGuild>(summaryModels.Count); | |||||
| foreach (var summaryModel in summaryModels) | |||||
| { | |||||
| var guildModel = await ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false); | |||||
| if (guildModel != null) | |||||
| guilds.Add(RestGuild.Create(this, guildModel)); | |||||
| } | |||||
| return guilds.ToImmutable(); | |||||
| } | |||||
| public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync() | |||||
| => ClientHelper.GetGuildsAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) | |||||
| { | |||||
| var args = new CreateGuildParams(name, region.Id); | |||||
| var model = await ApiClient.CreateGuildAsync(args).ConfigureAwait(false); | |||||
| return RestGuild.Create(this, model); | |||||
| } | |||||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) | |||||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<RestUser> GetUserAsync(ulong id) | |||||
| { | |||||
| var model = await ApiClient.GetUserAsync(id).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return RestUser.Create(this, model); | |||||
| return null; | |||||
| } | |||||
| public Task<RestUser> GetUserAsync(ulong id) | |||||
| => ClientHelper.GetUserAsync(this, id); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<RestUser> GetUserAsync(string username, string discriminator) | |||||
| { | |||||
| var model = await ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false); | |||||
| if (model != null) | |||||
| return RestUser.Create(this, model); | |||||
| return null; | |||||
| } | |||||
| public Task<RestUser> GetUserAsync(string username, string discriminator) | |||||
| => ClientHelper.GetUserAsync(this, username, discriminator); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual async Task<IReadOnlyCollection<RestUser>> QueryUsersAsync(string query, int limit) | |||||
| { | |||||
| var models = await ApiClient.QueryUsersAsync(query, limit).ConfigureAwait(false); | |||||
| return models.Select(x => RestUser.Create(this, x)).ToImmutableArray(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public virtual async Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync() | |||||
| { | |||||
| var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); | |||||
| return models.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableArray(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public virtual async Task<RestVoiceRegion> GetVoiceRegionAsync(string id) | |||||
| { | |||||
| var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); | |||||
| return models.Select(x => RestVoiceRegion.Create(this, x)).Where(x => x.Id == id).FirstOrDefault(); | |||||
| } | |||||
| internal virtual void Dispose(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| ApiClient.Dispose(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync() | |||||
| => ClientHelper.GetVoiceRegionsAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public void Dispose() => Dispose(true); | |||||
| private async Task WriteInitialLog() | |||||
| { | |||||
| /*if (this is DiscordSocketClient) | |||||
| await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, {DiscordSocketConfig.GatewayEncoding})").ConfigureAwait(false); | |||||
| else if (this is DiscordRpcClient) | |||||
| await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, RPC API v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false);*/ | |||||
| await _clientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); | |||||
| await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); | |||||
| await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); | |||||
| await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false); | |||||
| } | |||||
| private static string ToArchString(Architecture arch) | |||||
| { | |||||
| switch (arch) | |||||
| { | |||||
| case Architecture.X64: return "x64"; | |||||
| case Architecture.X86: return "x86"; | |||||
| default: return arch.ToString(); | |||||
| } | |||||
| } | |||||
| public Task<RestVoiceRegion> GetVoiceRegionAsync(string id) | |||||
| => ClientHelper.GetVoiceRegionAsync(this, id); | |||||
| //IDiscordClient | //IDiscordClient | ||||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | |||||
| ISelfUser IDiscordClient.CurrentUser => CurrentUser; | |||||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync() | |||||
| => await GetApplicationInfoAsync().ConfigureAwait(false); | |||||
| async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id) | |||||
| => await GetChannelAsync(id); | |||||
| async Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync() | |||||
| => await GetPrivateChannelsAsync(); | |||||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync() | async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync() | ||||
| => await GetConnectionsAsync().ConfigureAwait(false); | => await GetConnectionsAsync().ConfigureAwait(false); | ||||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId) | async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId) | ||||
| => await GetInviteAsync(inviteId).ConfigureAwait(false); | => await GetInviteAsync(inviteId).ConfigureAwait(false); | ||||
| async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id) | async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id) | ||||
| => await GetGuildAsync(id).ConfigureAwait(false); | => await GetGuildAsync(id).ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IUserGuild>> IDiscordClient.GetGuildSummariesAsync() | |||||
| => await GetGuildSummariesAsync().ConfigureAwait(false); | |||||
| async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync() | async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync() | ||||
| => await GetGuildsAsync().ConfigureAwait(false); | => await GetGuildsAsync().ConfigureAwait(false); | ||||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) | async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) | ||||
| => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | ||||
| async Task<IUser> IDiscordClient.GetUserAsync(ulong id) | async Task<IUser> IDiscordClient.GetUserAsync(ulong id) | ||||
| => await GetUserAsync(id).ConfigureAwait(false); | => await GetUserAsync(id).ConfigureAwait(false); | ||||
| async Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator) | async Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator) | ||||
| => await GetUserAsync(username, discriminator).ConfigureAwait(false); | => await GetUserAsync(username, discriminator).ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IUser>> IDiscordClient.QueryUsersAsync(string query, int limit) | |||||
| => await QueryUsersAsync(query, limit).ConfigureAwait(false); | |||||
| async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync() | async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync() | ||||
| => await GetVoiceRegionsAsync().ConfigureAwait(false); | => await GetVoiceRegionsAsync().ConfigureAwait(false); | ||||
| async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id) | async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id) | ||||
| => await GetVoiceRegionAsync(id).ConfigureAwait(false); | => await GetVoiceRegionAsync(id).ConfigureAwait(false); | ||||
| Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); } | |||||
| Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); } | |||||
| } | } | ||||
| } | } | ||||
| @@ -12,33 +12,33 @@ namespace Discord.Rest | |||||
| internal static class ChannelHelper | internal static class ChannelHelper | ||||
| { | { | ||||
| //General | //General | ||||
| public static async Task<Model> GetAsync(IGuildChannel channel, DiscordRestClient client) | |||||
| public static async Task<Model> GetAsync(IGuildChannel channel, DiscordClient client) | |||||
| { | { | ||||
| return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); | return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task<Model> GetAsync(IPrivateChannel channel, DiscordRestClient client) | |||||
| public static async Task<Model> GetAsync(IPrivateChannel channel, DiscordClient client) | |||||
| { | { | ||||
| return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); | return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task DeleteAsync(IChannel channel, DiscordRestClient client) | |||||
| public static async Task DeleteAsync(IChannel channel, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); | await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task ModifyAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static async Task ModifyAsync(IGuildChannel channel, DiscordClient client, | |||||
| Action<ModifyGuildChannelParams> func) | Action<ModifyGuildChannelParams> func) | ||||
| { | { | ||||
| var args = new ModifyGuildChannelParams(); | var args = new ModifyGuildChannelParams(); | ||||
| func(args); | func(args); | ||||
| await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); | await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); | ||||
| } | } | ||||
| public static async Task ModifyAsync(ITextChannel channel, DiscordRestClient client, | |||||
| public static async Task ModifyAsync(ITextChannel channel, DiscordClient client, | |||||
| Action<ModifyTextChannelParams> func) | Action<ModifyTextChannelParams> func) | ||||
| { | { | ||||
| var args = new ModifyTextChannelParams(); | var args = new ModifyTextChannelParams(); | ||||
| func(args); | func(args); | ||||
| await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); | await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); | ||||
| } | } | ||||
| public static async Task ModifyAsync(IVoiceChannel channel, DiscordRestClient client, | |||||
| public static async Task ModifyAsync(IVoiceChannel channel, DiscordClient client, | |||||
| Action<ModifyVoiceChannelParams> func) | Action<ModifyVoiceChannelParams> func) | ||||
| { | { | ||||
| var args = new ModifyVoiceChannelParams(); | var args = new ModifyVoiceChannelParams(); | ||||
| @@ -47,12 +47,12 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Invites | //Invites | ||||
| public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IChannel channel, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IChannel channel, DiscordClient client) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); | var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); | ||||
| return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| public static async Task<RestInviteMetadata> CreateInviteAsync(IChannel channel, DiscordRestClient client, | |||||
| public static async Task<RestInviteMetadata> CreateInviteAsync(IChannel channel, DiscordClient client, | |||||
| int? maxAge, int? maxUses, bool isTemporary) | int? maxAge, int? maxUses, bool isTemporary) | ||||
| { | { | ||||
| var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; | var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; | ||||
| @@ -65,13 +65,13 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Messages | //Messages | ||||
| public static async Task<RestMessage> GetMessageAsync(IChannel channel, DiscordRestClient client, | |||||
| public static async Task<RestMessage> GetMessageAsync(IChannel channel, DiscordClient client, | |||||
| ulong id) | ulong id) | ||||
| { | { | ||||
| var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); | var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); | ||||
| return RestMessage.Create(client, model); | return RestMessage.Create(client, model); | ||||
| } | } | ||||
| public static PagedAsyncEnumerable<RestMessage> GetMessagesAsync(IChannel channel, DiscordRestClient client, | |||||
| public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IChannel channel, DiscordClient client, | |||||
| ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) | ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
| { | { | ||||
| //TODO: Test this with Around direction | //TODO: Test this with Around direction | ||||
| @@ -102,13 +102,13 @@ namespace Discord.Rest | |||||
| count: (uint)limit | count: (uint)limit | ||||
| ); | ); | ||||
| } | } | ||||
| public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IChannel channel, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IChannel channel, DiscordClient client) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); | var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); | ||||
| return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| public static async Task<RestUserMessage> SendMessageAsync(IChannel channel, DiscordRestClient client, | |||||
| public static async Task<RestUserMessage> SendMessageAsync(IChannel channel, DiscordClient client, | |||||
| string text, bool isTTS) | string text, bool isTTS) | ||||
| { | { | ||||
| var args = new CreateMessageParams(text) { IsTTS = isTTS }; | var args = new CreateMessageParams(text) { IsTTS = isTTS }; | ||||
| @@ -116,14 +116,14 @@ namespace Discord.Rest | |||||
| return RestUserMessage.Create(client, model); | return RestUserMessage.Create(client, model); | ||||
| } | } | ||||
| public static Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordRestClient client, | |||||
| public static Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordClient client, | |||||
| string filePath, string text, bool isTTS) | string filePath, string text, bool isTTS) | ||||
| { | { | ||||
| string filename = Path.GetFileName(filePath); | string filename = Path.GetFileName(filePath); | ||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| return SendFileAsync(channel, client, file, filename, text, isTTS); | return SendFileAsync(channel, client, file, filename, text, isTTS); | ||||
| } | } | ||||
| public static async Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordRestClient client, | |||||
| public static async Task<RestUserMessage> SendFileAsync(IChannel channel, DiscordClient client, | |||||
| Stream stream, string filename, string text, bool isTTS) | Stream stream, string filename, string text, bool isTTS) | ||||
| { | { | ||||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| @@ -131,7 +131,7 @@ namespace Discord.Rest | |||||
| return RestUserMessage.Create(client, model); | return RestUserMessage.Create(client, model); | ||||
| } | } | ||||
| public static async Task DeleteMessagesAsync(IChannel channel, DiscordRestClient client, | |||||
| public static async Task DeleteMessagesAsync(IChannel channel, DiscordClient client, | |||||
| IEnumerable<IMessage> messages) | IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); | var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); | ||||
| @@ -139,31 +139,31 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Permission Overwrites | //Permission Overwrites | ||||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, | |||||
| IUser user, OverwritePermissions perms) | IUser user, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); | var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); | ||||
| await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false); | await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, | |||||
| IRole role, OverwritePermissions perms) | IRole role, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); | var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); | ||||
| await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false); | await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, | |||||
| IUser user) | IUser user) | ||||
| { | { | ||||
| await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); | await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, | |||||
| IRole role) | IRole role) | ||||
| { | { | ||||
| await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); | await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| //Users | //Users | ||||
| public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, DiscordClient client, | |||||
| ulong id) | ulong id) | ||||
| { | { | ||||
| var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); | var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); | ||||
| @@ -175,7 +175,7 @@ namespace Discord.Rest | |||||
| return user; | return user; | ||||
| } | } | ||||
| public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, DiscordRestClient client, | |||||
| public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, DiscordClient client, | |||||
| ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) | ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) | ||||
| { | { | ||||
| return new PagedAsyncEnumerable<RestGuildUser>( | return new PagedAsyncEnumerable<RestGuildUser>( | ||||
| @@ -203,7 +203,7 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Typing | //Typing | ||||
| public static IDisposable EnterTypingState(IChannel channel, DiscordRestClient client) | |||||
| public static IDisposable EnterTypingState(IChannel channel, DiscordClient client) | |||||
| { | { | ||||
| throw new NotImplementedException(); //TODO: Impl | throw new NotImplementedException(); //TODO: Impl | ||||
| } | } | ||||
| @@ -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 | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestDMChannel : RestEntity<ulong>, IDMChannel, IUpdateable | |||||
| public class RestDMChannel : RestChannel, IDMChannel, IUpdateable | |||||
| { | { | ||||
| public RestUser Recipient { get; } | |||||
| public RestUser CurrentUser { get; private set; } | |||||
| public RestUser Recipient { get; private set; } | |||||
| public IReadOnlyCollection<RestUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||||
| public IReadOnlyCollection<RestUser> Users => ImmutableArray.Create(CurrentUser, Recipient); | |||||
| internal RestDMChannel(DiscordRestClient discord, ulong id) | |||||
| internal RestDMChannel(DiscordClient discord, ulong id, ulong recipientId) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| Recipient = new RestUser(Discord, recipientId); | |||||
| CurrentUser = new RestUser(Discord, discord.CurrentUser.Id); | |||||
| } | } | ||||
| internal static RestDMChannel Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestDMChannel Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestDMChannel(discord, model.Id); | |||||
| var entity = new RestDMChannel(discord, model.Id, model.Recipients.Value[0].Id); | |||||
| entity.Update(model); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | |||||
| internal override void Update(Model model) | |||||
| { | { | ||||
| Recipient.Update(model.Recipients.Value[0]); | Recipient.Update(model.Recipients.Value[0]); | ||||
| } | } | ||||
| public async Task UpdateAsync() | |||||
| public override async Task UpdateAsync() | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | => Update(await ChannelHelper.GetAsync(this, Discord)); | ||||
| public Task CloseAsync() | public Task CloseAsync() | ||||
| => ChannelHelper.DeleteAsync(this, Discord); | => ChannelHelper.DeleteAsync(this, Discord); | ||||
| @@ -41,7 +44,7 @@ namespace Discord.Rest | |||||
| if (id == Recipient.Id) | if (id == Recipient.Id) | ||||
| return Recipient; | return Recipient; | ||||
| else if (id == Discord.CurrentUser.Id) | else if (id == Discord.CurrentUser.Id) | ||||
| return Discord.CurrentUser; | |||||
| return CurrentUser; | |||||
| else | else | ||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestGroupChannel : RestEntity<ulong>, IGroupChannel, IUpdateable | |||||
| public class RestGroupChannel : RestChannel, IGroupChannel, IUpdateable | |||||
| { | { | ||||
| private string _iconId; | private string _iconId; | ||||
| private ImmutableDictionary<ulong, RestGroupUser> _users; | private ImmutableDictionary<ulong, RestGroupUser> _users; | ||||
| @@ -21,17 +21,17 @@ namespace Discord.Rest | |||||
| public IReadOnlyCollection<RestGroupUser> Recipients | public IReadOnlyCollection<RestGroupUser> Recipients | ||||
| => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); | => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); | ||||
| internal RestGroupChannel(DiscordRestClient discord, ulong id) | |||||
| internal RestGroupChannel(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestGroupChannel Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestGroupChannel Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestGroupChannel(discord, model.Id); | var entity = new RestGroupChannel(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | |||||
| internal override void Update(Model model) | |||||
| { | { | ||||
| if (model.Name.IsSpecified) | if (model.Name.IsSpecified) | ||||
| Name = model.Name.Value; | Name = model.Name.Value; | ||||
| @@ -49,7 +49,7 @@ namespace Discord.Rest | |||||
| _users = users.ToImmutable(); | _users = users.ToImmutable(); | ||||
| } | } | ||||
| public async Task UpdateAsync() | |||||
| public override async Task UpdateAsync() | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | => Update(await ChannelHelper.GetAsync(this, Discord)); | ||||
| public Task LeaveAsync() | public Task LeaveAsync() | ||||
| => ChannelHelper.DeleteAsync(this, Discord); | => ChannelHelper.DeleteAsync(this, Discord); | ||||
| @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public abstract class RestGuildChannel : RestEntity<ulong>, IGuildChannel, IUpdateable | |||||
| public abstract class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable | |||||
| { | { | ||||
| private ImmutableArray<Overwrite> _overwrites; | private ImmutableArray<Overwrite> _overwrites; | ||||
| @@ -21,12 +21,12 @@ namespace Discord.Rest | |||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| internal RestGuildChannel(DiscordRestClient discord, ulong id, ulong guildId) | |||||
| internal RestGuildChannel(DiscordClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| GuildId = guildId; | GuildId = guildId; | ||||
| } | } | ||||
| internal static RestGuildChannel Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestGuildChannel Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| switch (model.Type) | switch (model.Type) | ||||
| { | { | ||||
| @@ -38,7 +38,7 @@ namespace Discord.Rest | |||||
| throw new InvalidOperationException("Unknown guild channel type"); | throw new InvalidOperationException("Unknown guild channel type"); | ||||
| } | } | ||||
| } | } | ||||
| internal virtual void Update(Model model) | |||||
| internal override void Update(Model model) | |||||
| { | { | ||||
| Name = model.Name.Value; | Name = model.Name.Value; | ||||
| Position = model.Position.Value; | Position = model.Position.Value; | ||||
| @@ -50,7 +50,7 @@ namespace Discord.Rest | |||||
| _overwrites = newOverwrites.ToImmutable(); | _overwrites = newOverwrites.ToImmutable(); | ||||
| } | } | ||||
| public async Task UpdateAsync() | |||||
| public override async Task UpdateAsync() | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | => Update(await ChannelHelper.GetAsync(this, Discord)); | ||||
| public Task ModifyAsync(Action<ModifyGuildChannelParams> func) | public Task ModifyAsync(Action<ModifyGuildChannelParams> func) | ||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | => ChannelHelper.ModifyAsync(this, Discord, func); | ||||
| @@ -16,11 +16,11 @@ namespace Discord.Rest | |||||
| public string Mention => MentionUtils.MentionChannel(Id); | public string Mention => MentionUtils.MentionChannel(Id); | ||||
| internal RestTextChannel(DiscordRestClient discord, ulong id, ulong guildId) | |||||
| internal RestTextChannel(DiscordClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id, guildId) | : base(discord, id, guildId) | ||||
| { | { | ||||
| } | } | ||||
| internal new static RestTextChannel Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestTextChannel Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); | var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -16,11 +16,11 @@ namespace Discord.Rest | |||||
| public int Bitrate { get; private set; } | public int Bitrate { get; private set; } | ||||
| public int UserLimit { get; private set; } | public int UserLimit { get; private set; } | ||||
| internal RestVoiceChannel(DiscordRestClient discord, ulong id, ulong guildId) | |||||
| internal RestVoiceChannel(DiscordClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id, guildId) | : base(discord, id, guildId) | ||||
| { | { | ||||
| } | } | ||||
| internal new static RestVoiceChannel Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestVoiceChannel Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); | var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Rest | |||||
| internal static class GuildHelper | internal static class GuildHelper | ||||
| { | { | ||||
| //General | //General | ||||
| public static async Task<Model> ModifyAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<Model> ModifyAsync(IGuild guild, DiscordClient client, | |||||
| Action<ModifyGuildParams> func) | Action<ModifyGuildParams> func) | ||||
| { | { | ||||
| if (func == null) throw new NullReferenceException(nameof(func)); | if (func == null) throw new NullReferenceException(nameof(func)); | ||||
| @@ -28,7 +28,7 @@ namespace Discord.Rest | |||||
| return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); | return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, DiscordClient client, | |||||
| Action<ModifyGuildEmbedParams> func) | Action<ModifyGuildEmbedParams> func) | ||||
| { | { | ||||
| if (func == null) throw new NullReferenceException(nameof(func)); | if (func == null) throw new NullReferenceException(nameof(func)); | ||||
| @@ -37,46 +37,46 @@ namespace Discord.Rest | |||||
| func(args); | func(args); | ||||
| return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); | return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task ModifyChannelsAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task ModifyChannelsAsync(IGuild guild, DiscordClient client, | |||||
| IEnumerable<ModifyGuildChannelsParams> args) | IEnumerable<ModifyGuildChannelsParams> args) | ||||
| { | { | ||||
| await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); | await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task<IReadOnlyCollection<RoleModel>> ModifyRolesAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<IReadOnlyCollection<RoleModel>> ModifyRolesAsync(IGuild guild, DiscordClient client, | |||||
| IEnumerable<ModifyGuildRolesParams> args) | IEnumerable<ModifyGuildRolesParams> args) | ||||
| { | { | ||||
| return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); | return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task LeaveAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task LeaveAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); | await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task DeleteAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task DeleteAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); | await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| //Bans | //Bans | ||||
| public static async Task<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetGuildBansAsync(guild.Id); | var models = await client.ApiClient.GetGuildBansAsync(guild.Id); | ||||
| return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| public static async Task AddBanAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task AddBanAsync(IGuild guild, DiscordClient client, | |||||
| ulong userId, int pruneDays) | ulong userId, int pruneDays) | ||||
| { | { | ||||
| var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; | var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; | ||||
| await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); | await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); | ||||
| } | } | ||||
| public static async Task RemoveBanAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task RemoveBanAsync(IGuild guild, DiscordClient client, | |||||
| ulong userId) | ulong userId) | ||||
| { | { | ||||
| await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); | await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); | ||||
| } | } | ||||
| //Channels | //Channels | ||||
| public static async Task<RestGuildChannel> GetChannelAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<RestGuildChannel> GetChannelAsync(IGuild guild, DiscordClient client, | |||||
| ulong id) | ulong id) | ||||
| { | { | ||||
| var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); | var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); | ||||
| @@ -84,12 +84,12 @@ namespace Discord.Rest | |||||
| return RestGuildChannel.Create(client, model); | return RestGuildChannel.Create(client, model); | ||||
| return null; | return null; | ||||
| } | } | ||||
| public static async Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); | var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); | ||||
| return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, DiscordClient client, | |||||
| string name) | string name) | ||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| @@ -98,7 +98,7 @@ namespace Discord.Rest | |||||
| var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); | var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); | ||||
| return RestTextChannel.Create(client, model); | return RestTextChannel.Create(client, model); | ||||
| } | } | ||||
| public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, DiscordClient client, | |||||
| string name) | string name) | ||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| @@ -109,12 +109,12 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Integrations | //Integrations | ||||
| public static async Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false); | var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false); | ||||
| return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| public static async Task<RestGuildIntegration> CreateIntegrationAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<RestGuildIntegration> CreateIntegrationAsync(IGuild guild, DiscordClient client, | |||||
| ulong id, string type) | ulong id, string type) | ||||
| { | { | ||||
| var args = new CreateGuildIntegrationParams(id, type); | var args = new CreateGuildIntegrationParams(id, type); | ||||
| @@ -123,14 +123,14 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Invites | //Invites | ||||
| public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false); | var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false); | ||||
| return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| //Roles | //Roles | ||||
| public static async Task<RestRole> CreateRoleAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<RestRole> CreateRoleAsync(IGuild guild, DiscordClient client, | |||||
| string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) | string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) | ||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| @@ -150,7 +150,7 @@ namespace Discord.Rest | |||||
| } | } | ||||
| //Users | //Users | ||||
| public static async Task<RestGuildUser> GetUserAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<RestGuildUser> GetUserAsync(IGuild guild, DiscordClient client, | |||||
| ulong id) | ulong id) | ||||
| { | { | ||||
| var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); | var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); | ||||
| @@ -158,17 +158,17 @@ namespace Discord.Rest | |||||
| return RestGuildUser.Create(client, model); | return RestGuildUser.Create(client, model); | ||||
| return null; | return null; | ||||
| } | } | ||||
| public static async Task<RestGuildUser> GetCurrentUserAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task<RestGuildUser> GetCurrentUserAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); | return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuild guild, DiscordRestClient client) | |||||
| public static async Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuild guild, DiscordClient client) | |||||
| { | { | ||||
| var args = new GetGuildMembersParams(); | var args = new GetGuildMembersParams(); | ||||
| var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); | var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); | ||||
| return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); | return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); | ||||
| } | } | ||||
| public static async Task<int> PruneUsersAsync(IGuild guild, DiscordRestClient client, | |||||
| public static async Task<int> PruneUsersAsync(IGuild guild, DiscordClient client, | |||||
| int days = 30, bool simulate = false) | int days = 30, bool simulate = false) | ||||
| { | { | ||||
| var args = new GuildPruneParams(days); | var args = new GuildPruneParams(days); | ||||
| @@ -14,7 +14,7 @@ namespace Discord.Rest | |||||
| User = user; | User = user; | ||||
| Reason = reason; | Reason = reason; | ||||
| } | } | ||||
| internal static RestBan Create(DiscordRestClient client, Model model) | |||||
| internal static RestBan Create(DiscordClient client, Model model) | |||||
| { | { | ||||
| return new RestBan(RestUser.Create(client, model.User), model.Reason); | return new RestBan(RestUser.Create(client, model.User), model.Reason); | ||||
| } | } | ||||
| @@ -39,11 +39,11 @@ namespace Discord.Rest | |||||
| public IReadOnlyCollection<Emoji> Emojis => _emojis; | public IReadOnlyCollection<Emoji> Emojis => _emojis; | ||||
| public IReadOnlyCollection<string> Features => _features; | public IReadOnlyCollection<string> Features => _features; | ||||
| internal RestGuild(DiscordRestClient client, ulong id) | |||||
| internal RestGuild(DiscordClient client, ulong id) | |||||
| : base(client, id) | : base(client, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestGuild Create(DiscordRestClient discord, Model model) | |||||
| internal static RestGuild Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestGuild(discord, model.Id); | var entity = new RestGuild(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -25,11 +25,11 @@ namespace Discord.Rest | |||||
| public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); | public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); | ||||
| internal RestGuildIntegration(DiscordRestClient discord, ulong id) | |||||
| internal RestGuildIntegration(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestGuildIntegration Create(DiscordRestClient discord, Model model) | |||||
| internal static RestGuildIntegration Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestGuildIntegration(discord, model.Id); | var entity = new RestGuildIntegration(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -15,11 +15,11 @@ namespace Discord.Rest | |||||
| public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); | public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); | ||||
| internal RestUserGuild(DiscordRestClient discord, ulong id) | |||||
| internal RestUserGuild(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestUserGuild Create(DiscordRestClient discord, Model model) | |||||
| internal static RestUserGuild Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestUserGuild(discord, model.Id); | var entity = new RestUserGuild(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -13,11 +13,11 @@ namespace Discord | |||||
| public string SampleHostname { get; private set; } | public string SampleHostname { get; private set; } | ||||
| public int SamplePort { get; private set; } | public int SamplePort { get; private set; } | ||||
| internal RestVoiceRegion(DiscordRestClient client, string id) | |||||
| internal RestVoiceRegion(DiscordClient client, string id) | |||||
| : base(client, id) | : base(client, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestVoiceRegion Create(DiscordRestClient client, Model model) | |||||
| internal static RestVoiceRegion Create(DiscordClient client, Model model) | |||||
| { | { | ||||
| var entity = new RestVoiceRegion(client, model.Id); | var entity = new RestVoiceRegion(client, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -5,15 +5,15 @@ namespace Discord.Rest | |||||
| { | { | ||||
| internal static class InviteHelper | internal static class InviteHelper | ||||
| { | { | ||||
| public static async Task<Model> GetAsync(IInvite invite, DiscordRestClient client) | |||||
| public static async Task<Model> GetAsync(IInvite invite, DiscordClient client) | |||||
| { | { | ||||
| return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); | return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task AcceptAsync(IInvite invite, DiscordRestClient client) | |||||
| public static async Task AcceptAsync(IInvite invite, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); | await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task DeleteAsync(IInvite invite, DiscordRestClient client) | |||||
| public static async Task DeleteAsync(IInvite invite, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); | await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -16,11 +16,11 @@ namespace Discord.Rest | |||||
| public string Code => Id; | public string Code => Id; | ||||
| public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; | public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; | ||||
| internal RestInvite(DiscordRestClient discord, string id) | |||||
| internal RestInvite(DiscordClient discord, string id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestInvite Create(DiscordRestClient discord, Model model) | |||||
| internal static RestInvite Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestInvite(discord, model.Code); | var entity = new RestInvite(discord, model.Code); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -18,11 +18,11 @@ namespace Discord.Rest | |||||
| public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | ||||
| internal RestInviteMetadata(DiscordRestClient discord, string id) | |||||
| internal RestInviteMetadata(DiscordClient discord, string id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestInviteMetadata Create(DiscordRestClient discord, Model model) | |||||
| internal static RestInviteMetadata Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestInviteMetadata(discord, model.Code); | var entity = new RestInviteMetadata(discord, model.Code); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -6,26 +6,26 @@ namespace Discord.Rest | |||||
| { | { | ||||
| internal static class MessageHelper | internal static class MessageHelper | ||||
| { | { | ||||
| public static async Task GetAsync(IMessage msg, DiscordRestClient client) | |||||
| public static async Task GetAsync(IMessage msg, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); | await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); | ||||
| } | } | ||||
| public static async Task ModifyAsync(IMessage msg, DiscordRestClient client, Action<ModifyMessageParams> func) | |||||
| public static async Task ModifyAsync(IMessage msg, DiscordClient client, Action<ModifyMessageParams> func) | |||||
| { | { | ||||
| var args = new ModifyMessageParams(); | var args = new ModifyMessageParams(); | ||||
| func(args); | func(args); | ||||
| await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); | await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); | ||||
| } | } | ||||
| public static async Task DeleteAsync(IMessage msg, DiscordRestClient client) | |||||
| public static async Task DeleteAsync(IMessage msg, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); | await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); | ||||
| } | } | ||||
| public static async Task PinAsync(IMessage msg, DiscordRestClient client) | |||||
| public static async Task PinAsync(IMessage msg, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); | await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); | ||||
| } | } | ||||
| public static async Task UnpinAsync(IMessage msg, DiscordRestClient client) | |||||
| public static async Task UnpinAsync(IMessage msg, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); | await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| //TODO: Rename to Attachment? | |||||
| public class RestAttachment : IAttachment | public class RestAttachment : IAttachment | ||||
| { | { | ||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| @@ -29,12 +29,12 @@ namespace Discord.Rest | |||||
| public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | ||||
| internal RestMessage(DiscordRestClient discord, ulong id, ulong channelId) | |||||
| internal RestMessage(DiscordClient discord, ulong id, ulong channelId) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| } | } | ||||
| internal static RestMessage Create(DiscordRestClient discord, Model model) | |||||
| internal static RestMessage Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| if (model.Type == MessageType.Default) | if (model.Type == MessageType.Default) | ||||
| return RestUserMessage.Create(discord, model); | return RestUserMessage.Create(discord, model); | ||||
| @@ -8,11 +8,11 @@ namespace Discord.Rest | |||||
| { | { | ||||
| public MessageType Type { get; private set; } | public MessageType Type { get; private set; } | ||||
| internal RestSystemMessage(DiscordRestClient discord, ulong id, ulong channelId) | |||||
| internal RestSystemMessage(DiscordClient discord, ulong id, ulong channelId) | |||||
| : base(discord, id, channelId) | : base(discord, id, channelId) | ||||
| { | { | ||||
| } | } | ||||
| internal new static RestSystemMessage Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestSystemMessage Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); | var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -30,11 +30,11 @@ namespace Discord.Rest | |||||
| public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles; | public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles; | ||||
| public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers; | public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers; | ||||
| internal RestUserMessage(DiscordRestClient discord, ulong id, ulong channelId) | |||||
| internal RestUserMessage(DiscordClient discord, ulong id, ulong channelId) | |||||
| : base(discord, id, channelId) | : base(discord, id, channelId) | ||||
| { | { | ||||
| } | } | ||||
| internal new static RestUserMessage Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestUserMessage Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestUserMessage(discord, model.Id, model.ChannelId); | var entity = new RestUserMessage(discord, model.Id, model.ChannelId); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -17,11 +17,11 @@ namespace Discord.Rest | |||||
| public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); | public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); | ||||
| internal RestApplication(DiscordRestClient discord, ulong id) | |||||
| internal RestApplication(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestApplication Create(DiscordRestClient discord, Model model) | |||||
| internal static RestApplication Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestApplication(discord, model.Id); | var entity = new RestApplication(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -5,10 +5,10 @@ namespace Discord.Rest | |||||
| public abstract class RestEntity<T> : IEntity<T> | public abstract class RestEntity<T> : IEntity<T> | ||||
| where T : IEquatable<T> | where T : IEquatable<T> | ||||
| { | { | ||||
| public DiscordClient Discord { get; } | |||||
| public T Id { get; } | public T Id { get; } | ||||
| public DiscordRestClient Discord { get; } | |||||
| public RestEntity(DiscordRestClient discord, T id) | |||||
| internal RestEntity(DiscordClient discord, T id) | |||||
| { | { | ||||
| Discord = discord; | Discord = discord; | ||||
| Id = id; | Id = id; | ||||
| @@ -21,11 +21,11 @@ namespace Discord.Rest | |||||
| public bool IsEveryone => Id == Guild.Id; | public bool IsEveryone => Id == Guild.Id; | ||||
| public string Mention => MentionUtils.MentionRole(Id); | public string Mention => MentionUtils.MentionRole(Id); | ||||
| internal RestRole(DiscordRestClient discord, ulong id) | |||||
| internal RestRole(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestRole Create(DiscordRestClient discord, Model model) | |||||
| internal static RestRole Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestRole(discord, model.Id); | var entity = new RestRole(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -7,11 +7,11 @@ namespace Discord.Rest | |||||
| internal static class RoleHelper | internal static class RoleHelper | ||||
| { | { | ||||
| //General | //General | ||||
| public static async Task DeleteAsync(IRole role, DiscordRestClient client) | |||||
| public static async Task DeleteAsync(IRole role, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); | await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public static async Task ModifyAsync(IRole role, DiscordRestClient client, | |||||
| public static async Task ModifyAsync(IRole role, DiscordClient client, | |||||
| Action<ModifyGuildRoleParams> func) | Action<ModifyGuildRoleParams> func) | ||||
| { | { | ||||
| var args = new ModifyGuildRoleParams(); | var args = new ModifyGuildRoleParams(); | ||||
| @@ -6,11 +6,11 @@ namespace Discord.Rest | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestGroupUser : RestUser, IGroupUser | public class RestGroupUser : RestUser, IGroupUser | ||||
| { | { | ||||
| internal RestGroupUser(DiscordRestClient discord, ulong id) | |||||
| internal RestGroupUser(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal new static RestGroupUser Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestGroupUser Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestGroupUser(discord, model.Id); | var entity = new RestGroupUser(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -21,11 +21,11 @@ namespace Discord.Rest | |||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | ||||
| internal RestGuildUser(DiscordRestClient discord, ulong id) | |||||
| internal RestGuildUser(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestGuildUser Create(DiscordRestClient discord, Model model) | |||||
| internal static RestGuildUser Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestGuildUser(discord, model.User.Id); | var entity = new RestGuildUser(discord, model.User.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -13,11 +13,11 @@ namespace Discord.Rest | |||||
| public bool IsVerified { get; private set; } | public bool IsVerified { get; private set; } | ||||
| public bool IsMfaEnabled { get; private set; } | public bool IsMfaEnabled { get; private set; } | ||||
| internal RestSelfUser(DiscordRestClient discord, ulong id) | |||||
| internal RestSelfUser(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal new static RestSelfUser Create(DiscordRestClient discord, Model model) | |||||
| internal new static RestSelfUser Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestSelfUser(discord, model.Id); | var entity = new RestSelfUser(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -18,11 +18,11 @@ namespace Discord.Rest | |||||
| public virtual Game? Game => null; | public virtual Game? Game => null; | ||||
| public virtual UserStatus Status => UserStatus.Unknown; | public virtual UserStatus Status => UserStatus.Unknown; | ||||
| internal RestUser(DiscordRestClient discord, ulong id) | |||||
| internal RestUser(DiscordClient discord, ulong id) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestUser Create(DiscordRestClient discord, Model model) | |||||
| internal static RestUser Create(DiscordClient discord, Model model) | |||||
| { | { | ||||
| var entity = new RestUser(discord, model.Id); | var entity = new RestUser(discord, model.Id); | ||||
| entity.Update(model); | entity.Update(model); | ||||
| @@ -8,22 +8,22 @@ namespace Discord.Rest | |||||
| { | { | ||||
| internal static class UserHelper | internal static class UserHelper | ||||
| { | { | ||||
| public static async Task<Model> GetAsync(IUser user, DiscordRestClient client) | |||||
| public static async Task<Model> GetAsync(IUser user, DiscordClient client) | |||||
| { | { | ||||
| return await client.ApiClient.GetUserAsync(user.Id); | return await client.ApiClient.GetUserAsync(user.Id); | ||||
| } | } | ||||
| public static async Task<Model> GetAsync(ISelfUser user, DiscordRestClient client) | |||||
| public static async Task<Model> GetAsync(ISelfUser user, DiscordClient client) | |||||
| { | { | ||||
| var model = await client.ApiClient.GetMyUserAsync(); | var model = await client.ApiClient.GetMyUserAsync(); | ||||
| if (model.Id != user.Id) | if (model.Id != user.Id) | ||||
| throw new InvalidOperationException("Unable to update this object using a different token."); | throw new InvalidOperationException("Unable to update this object using a different token."); | ||||
| return model; | return model; | ||||
| } | } | ||||
| public static async Task<MemberModel> GetAsync(IGuildUser user, DiscordRestClient client) | |||||
| public static async Task<MemberModel> GetAsync(IGuildUser user, DiscordClient client) | |||||
| { | { | ||||
| return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); | return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); | ||||
| } | } | ||||
| public static async Task ModifyAsync(ISelfUser user, DiscordRestClient client, Action<ModifyCurrentUserParams> func) | |||||
| public static async Task ModifyAsync(ISelfUser user, DiscordClient client, Action<ModifyCurrentUserParams> func) | |||||
| { | { | ||||
| if (user.Id != client.CurrentUser.Id) | if (user.Id != client.CurrentUser.Id) | ||||
| throw new InvalidOperationException("Unable to modify this object using a different token."); | throw new InvalidOperationException("Unable to modify this object using a different token."); | ||||
| @@ -32,19 +32,19 @@ namespace Discord.Rest | |||||
| func(args); | func(args); | ||||
| await client.ApiClient.ModifySelfAsync(args); | await client.ApiClient.ModifySelfAsync(args); | ||||
| } | } | ||||
| public static async Task ModifyAsync(IGuildUser user, DiscordRestClient client, Action<ModifyGuildMemberParams> func) | |||||
| public static async Task ModifyAsync(IGuildUser user, DiscordClient client, Action<ModifyGuildMemberParams> func) | |||||
| { | { | ||||
| var args = new ModifyGuildMemberParams(); | var args = new ModifyGuildMemberParams(); | ||||
| func(args); | func(args); | ||||
| await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); | await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); | ||||
| } | } | ||||
| public static async Task KickAsync(IGuildUser user, DiscordRestClient client) | |||||
| public static async Task KickAsync(IGuildUser user, DiscordClient client) | |||||
| { | { | ||||
| await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); | await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); | ||||
| } | } | ||||
| public static async Task<IDMChannel> CreateDMChannelAsync(IUser user, DiscordRestClient client) | |||||
| public static async Task<IDMChannel> CreateDMChannelAsync(IUser user, DiscordClient client) | |||||
| { | { | ||||
| var args = new CreateDMChannelParams(user.Id); | var args = new CreateDMChannelParams(user.Id); | ||||
| return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); | return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); | ||||
| @@ -1,12 +1,6 @@ | |||||
| { | { | ||||
| "version": "1.0.0-beta2-*", | "version": "1.0.0-beta2-*", | ||||
| "buildOptions": { | |||||
| "compile": { | |||||
| "include": [ "../Discord.Net.Utils/**.cs" ] | |||||
| } | |||||
| }, | |||||
| "configurations": { | "configurations": { | ||||
| "Release": { | "Release": { | ||||
| "buildOptions": { | "buildOptions": { | ||||
| @@ -3,6 +3,7 @@ using Discord.API.Rpc; | |||||
| using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rpc; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| @@ -66,8 +67,8 @@ namespace Discord.API | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| public DiscordRpcApiClient(string clientId, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) | |||||
| : base(restClientProvider, serializer, requestQueue) | |||||
| public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) | |||||
| : base(restClientProvider, userAgent, serializer, requestQueue) | |||||
| { | { | ||||
| _connectionLock = new SemaphoreSlim(1, 1); | _connectionLock = new SemaphoreSlim(1, 1); | ||||
| _clientId = clientId; | _clientId = clientId; | ||||
| @@ -0,0 +1,3 @@ | |||||
| using System.Runtime.CompilerServices; | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Test")] | |||||
| @@ -11,9 +11,9 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Rpc | namespace Discord.Rpc | ||||
| { | { | ||||
| public partial class DiscordRpcClient : DiscordRestClient | |||||
| public partial class DiscordRpcClient : DiscordClient | |||||
| { | { | ||||
| private readonly ILogger _rpcLogger; | |||||
| private readonly Logger _rpcLogger; | |||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| private TaskCompletionSource<bool> _connectTask; | private TaskCompletionSource<bool> _connectTask; | ||||
| @@ -58,18 +58,7 @@ namespace Discord.Rpc | |||||
| }; | }; | ||||
| } | } | ||||
| private static API.DiscordRpcApiClient CreateApiClient(DiscordRpcConfig config) | private static API.DiscordRpcApiClient CreateApiClient(DiscordRpcConfig config) | ||||
| => new API.DiscordRpcApiClient(config.ClientId, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); | |||||
| internal override void Dispose(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| ApiClient.Dispose(); | |||||
| } | |||||
| protected override Task ValidateTokenAsync(TokenType tokenType, string token) | |||||
| { | |||||
| return Task.CompletedTask; //Validation is done in DiscordRpcAPIClient | |||||
| } | |||||
| => new API.DiscordRpcApiClient(config.ClientId, DiscordRestConfig.UserAgent, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task ConnectAsync() => ConnectAsync(false); | public Task ConnectAsync() => ConnectAsync(false); | ||||
| @@ -371,20 +360,20 @@ namespace Discord.Rpc | |||||
| //Messages | //Messages | ||||
| case "MESSAGE_CREATE": | case "MESSAGE_CREATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||||
| /*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||||
| var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | ||||
| var msg = new RpcMessage(this, data.Message); | var msg = new RpcMessage(this, data.Message); | ||||
| await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false); | |||||
| await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/ | |||||
| } | } | ||||
| break; | break; | ||||
| case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | |||||
| /*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | |||||
| var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | ||||
| var msg = new RpcMessage(this, data.Message); | var msg = new RpcMessage(this, data.Message); | ||||
| await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false); | |||||
| await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/ | |||||
| } | } | ||||
| break; | break; | ||||
| case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
| @@ -1,8 +0,0 @@ | |||||
| namespace Discord.Rpc | |||||
| { | |||||
| /*public interface IRemoteUserGuild : ISnowflakeEntity | |||||
| { | |||||
| /// <summary> Gets the name of this guild. </summary> | |||||
| string Name { get; } | |||||
| }*/ | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| namespace Discord.Rpc | |||||
| { | |||||
| /*internal class RpcMessage : RpcEntity<ulong>, IMessage | |||||
| { | |||||
| internal RpcMessage(DiscordRpcClient discord, API.Message model) | |||||
| : base(dicsord, model.Id) | |||||
| { | |||||
| } | |||||
| }*/ | |||||
| } | |||||
| @@ -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 | namespace Discord.Rpc | ||||
| { | { | ||||
| /*internal class RemoteUserGuild : IRemoteUserGuild, ISnowflakeEntity | |||||
| /*internal class RemoteUserGuild : RpcEntity, IRemoteUserGuild, ISnowflakeEntity | |||||
| { | { | ||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| public DiscordRestClient Discord { get; } | public DiscordRestClient Discord { get; } | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Rpc | |||||
| public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| public RemoteUserGuild(DiscordRestClient discord, Model model) | |||||
| internal RemoteUserGuild(DiscordRestClient discord, Model model) | |||||
| { | { | ||||
| Id = model.Id; | Id = model.Id; | ||||
| Discord = discord; | Discord = discord; | ||||
| @@ -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-*", | "version": "1.0.0-beta2-*", | ||||
| "buildOptions": { | |||||
| "compile": { | |||||
| "include": [ "../Discord.Net.Utils/**.cs" ] | |||||
| "configurations": { | |||||
| "Release": { | |||||
| "buildOptions": { | |||||
| "define": [ "RELEASE" ], | |||||
| "nowarn": [ "CS1573", "CS1591" ], | |||||
| "optimize": true, | |||||
| "warningsAsErrors": true, | |||||
| "xmlDoc": true | |||||
| } | |||||
| } | } | ||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "Discord.Net.Core": { | |||||
| "target": "project" | |||||
| }, | |||||
| "Discord.Net.Rest": { | |||||
| "target": "project" | |||||
| }, | |||||
| "NETStandard.Library": "1.6.0" | "NETStandard.Library": "1.6.0" | ||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| "netstandard1.6": { | |||||
| "imports": "dnxcore50" | |||||
| "netstandard1.3": { | |||||
| "imports": [ | |||||
| "dotnet5.4", | |||||
| "dnxcore50", | |||||
| "portable-net45+win8" | |||||
| ] | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -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.Queue; | ||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.WebSocket; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -31,8 +32,8 @@ namespace Discord.API | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) | |||||
| : base(restClientProvider, serializer, requestQueue) | |||||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, string userAgent, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) | |||||
| : base(restClientProvider, userAgent, serializer, requestQueue) | |||||
| { | { | ||||
| _gatewayClient = webSocketProvider(); | _gatewayClient = webSocketProvider(); | ||||
| //_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) | //_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) | ||||
| @@ -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; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Net; | using System.Net; | ||||
| using System.Text; | using System.Text; | ||||
| @@ -34,10 +35,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | ||||
| private readonly ILogger _audioLogger; | |||||
| #if BENCHMARK | |||||
| private readonly ILogger _benchmarkLogger; | |||||
| #endif | |||||
| private readonly Logger _audioLogger; | |||||
| internal readonly SemaphoreSlim _connectionLock; | internal readonly SemaphoreSlim _connectionLock; | ||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| @@ -63,9 +61,6 @@ namespace Discord.Audio | |||||
| Guild = guild; | Guild = guild; | ||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | _audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | ||||
| #if BENCHMARK | |||||
| _benchmarkLogger = logManager.CreateLogger("Benchmark"); | |||||
| #endif | |||||
| _connectionLock = new SemaphoreSlim(1, 1); | _connectionLock = new SemaphoreSlim(1, 1); | ||||
| @@ -181,11 +176,11 @@ namespace Discord.Audio | |||||
| ApiClient.SendAsync(data, count).ConfigureAwait(false); | ApiClient.SendAsync(data, count).ConfigureAwait(false); | ||||
| } | } | ||||
| public RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000) | |||||
| public Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000) | |||||
| { | { | ||||
| return new RTPWriteStream(this, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000); | return new RTPWriteStream(this, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000); | ||||
| } | } | ||||
| public OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, | |||||
| public Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, | |||||
| OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000) | OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000) | ||||
| { | { | ||||
| return new OpusEncodeStream(this, _secretKey, samplesPerFrame, _ssrc, bitrate, application, bufferSize); | return new OpusEncodeStream(this, _secretKey, samplesPerFrame, _ssrc, bitrate, application, bufferSize); | ||||
| @@ -193,11 +188,6 @@ namespace Discord.Audio | |||||
| private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) | private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) | ||||
| { | { | ||||
| #if BENCHMARK | |||||
| Stopwatch stopwatch = Stopwatch.StartNew(); | |||||
| try | |||||
| { | |||||
| #endif | |||||
| try | try | ||||
| { | { | ||||
| switch (opCode) | switch (opCode) | ||||
| @@ -262,15 +252,6 @@ namespace Discord.Audio | |||||
| await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); | await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| #if BENCHMARK | |||||
| } | |||||
| finally | |||||
| { | |||||
| stopwatch.Stop(); | |||||
| double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | |||||
| await _benchmarkLogger.DebugAsync($"{millis} ms").ConfigureAwait(false); | |||||
| } | |||||
| #endif | |||||
| } | } | ||||
| private async Task ProcessPacketAsync(byte[] packet) | private async Task ProcessPacketAsync(byte[] packet) | ||||
| { | { | ||||
| @@ -1,6 +1,6 @@ | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public class OpusDecodeStream : RTPReadStream | |||||
| internal class OpusDecodeStream : RTPReadStream | |||||
| { | { | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| private readonly OpusDecoder _decoder; | private readonly OpusDecoder _decoder; | ||||
| @@ -1,6 +1,6 @@ | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public class OpusEncodeStream : RTPWriteStream | |||||
| internal class OpusEncodeStream : RTPWriteStream | |||||
| { | { | ||||
| public int SampleRate = 48000; | public int SampleRate = 48000; | ||||
| public int Channels = 2; | public int Channels = 2; | ||||
| @@ -4,7 +4,7 @@ using System.IO; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public class RTPReadStream : Stream | |||||
| internal class RTPReadStream : Stream | |||||
| { | { | ||||
| private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer | private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer | ||||
| private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
| @@ -3,7 +3,7 @@ using System.IO; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public class RTPWriteStream : Stream | |||||
| internal class RTPWriteStream : Stream | |||||
| { | { | ||||
| private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
| private readonly byte[] _nonce, _secretKey; | private readonly byte[] _nonce, _secretKey; | ||||
| @@ -12,37 +12,37 @@ namespace Discord.WebSocket | |||||
| private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 | private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 | ||||
| private const double CollectionMultiplier = 1.05; //Add 5% buffer to handle growth | private const double CollectionMultiplier = 1.05; //Add 5% buffer to handle growth | ||||
| private readonly ConcurrentDictionary<ulong, ISocketChannel> _channels; | |||||
| private readonly ConcurrentDictionary<ulong, SocketChannel> _channels; | |||||
| private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; | private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; | ||||
| private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; | private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; | ||||
| private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; | private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; | ||||
| private readonly ConcurrentHashSet<ulong> _groupChannels; | private readonly ConcurrentHashSet<ulong> _groupChannels; | ||||
| internal IReadOnlyCollection<ISocketChannel> Channels => _channels.ToReadOnlyCollection(); | |||||
| internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection(); | |||||
| internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); | internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); | ||||
| internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => | |||||
| _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( | |||||
| _groupChannels.Select(x => GetChannel(x) as ISocketPrivateChannel)) | |||||
| internal IReadOnlyCollection<IPrivateChannel> PrivateChannels => | |||||
| _dmChannels.Select(x => x.Value as IPrivateChannel).Concat( | |||||
| _groupChannels.Select(x => GetChannel(x) as IPrivateChannel)) | |||||
| .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); | .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); | ||||
| public DataStore(int guildCount, int dmChannelCount) | public DataStore(int guildCount, int dmChannelCount) | ||||
| { | { | ||||
| double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; | double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; | ||||
| double estimatedUsersCount = guildCount * AverageUsersPerGuild; | double estimatedUsersCount = guildCount * AverageUsersPerGuild; | ||||
| _channels = new ConcurrentDictionary<ulong, ISocketChannel>(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier)); | |||||
| _channels = new ConcurrentDictionary<ulong, SocketChannel>(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier)); | |||||
| _dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(CollectionConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); | _dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(CollectionConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); | ||||
| _guilds = new ConcurrentDictionary<ulong, SocketGuild>(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); | _guilds = new ConcurrentDictionary<ulong, SocketGuild>(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); | ||||
| _users = new ConcurrentDictionary<ulong, SocketGlobalUser>(CollectionConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); | _users = new ConcurrentDictionary<ulong, SocketGlobalUser>(CollectionConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); | ||||
| _groupChannels = new ConcurrentHashSet<ulong>(CollectionConcurrencyLevel, (int)(10 * CollectionMultiplier)); | _groupChannels = new ConcurrentHashSet<ulong>(CollectionConcurrencyLevel, (int)(10 * CollectionMultiplier)); | ||||
| } | } | ||||
| internal ISocketChannel GetChannel(ulong id) | |||||
| internal SocketChannel GetChannel(ulong id) | |||||
| { | { | ||||
| ISocketChannel channel; | |||||
| SocketChannel channel; | |||||
| if (_channels.TryGetValue(id, out channel)) | if (_channels.TryGetValue(id, out channel)) | ||||
| return channel; | return channel; | ||||
| return null; | return null; | ||||
| @@ -54,7 +54,7 @@ namespace Discord.WebSocket | |||||
| return channel; | return channel; | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal void AddChannel(ISocketChannel channel) | |||||
| internal void AddChannel(SocketChannel channel) | |||||
| { | { | ||||
| _channels[channel.Id] = channel; | _channels[channel.Id] = channel; | ||||
| @@ -68,9 +68,9 @@ namespace Discord.WebSocket | |||||
| _groupChannels.TryAdd(groupChannel.Id); | _groupChannels.TryAdd(groupChannel.Id); | ||||
| } | } | ||||
| } | } | ||||
| internal ISocketChannel RemoveChannel(ulong id) | |||||
| internal SocketChannel RemoveChannel(ulong id) | |||||
| { | { | ||||
| ISocketChannel channel; | |||||
| SocketChannel channel; | |||||
| if (_channels.TryRemove(id, out channel)) | if (_channels.TryRemove(id, out channel)) | ||||
| { | { | ||||
| var dmChannel = channel as SocketDMChannel; | var dmChannel = channel as SocketDMChannel; | ||||
| @@ -4,18 +4,16 @@ | |||||
| <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | ||||
| <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | ||||
| <PropertyGroup Label="Globals"> | <PropertyGroup Label="Globals"> | ||||
| <ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid> | <ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid> | ||||
| <RootNamespace>Discord.Net.WebSocket</RootNamespace> | |||||
| <RootNamespace>Discord.WebSocket</RootNamespace> | |||||
| <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | ||||
| <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | ||||
| <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <SchemaVersion>2.0</SchemaVersion> | <SchemaVersion>2.0</SchemaVersion> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | ||||
| </Project> | |||||
| </Project> | |||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API.Gateway; | |||||
| using Discord.API; | |||||
| using Discord.API.Gateway; | |||||
| using Discord.Audio; | using Discord.Audio; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| @@ -11,24 +12,22 @@ using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| public partial class DiscordSocketClient : DiscordRestClient, IDiscordClient | |||||
| public partial class DiscordSocketClient : DiscordClient, IDiscordClient | |||||
| { | { | ||||
| private readonly ConcurrentQueue<ulong> _largeGuilds; | private readonly ConcurrentQueue<ulong> _largeGuilds; | ||||
| private readonly ILogger _gatewayLogger; | |||||
| #if BENCHMARK | |||||
| private readonly ILogger _benchmarkLogger; | |||||
| #endif | |||||
| private readonly Logger _gatewayLogger; | |||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| private string _sessionId; | private string _sessionId; | ||||
| private int _lastSeq; | private int _lastSeq; | ||||
| private ImmutableDictionary<string, VoiceRegion> _voiceRegions; | |||||
| private ImmutableDictionary<string, RestVoiceRegion> _voiceRegions; | |||||
| private TaskCompletionSource<bool> _connectTask; | private TaskCompletionSource<bool> _connectTask; | ||||
| private CancellationTokenSource _cancelToken, _reconnectCancelToken; | private CancellationTokenSource _cancelToken, _reconnectCancelToken; | ||||
| private Task _heartbeatTask, _guildDownloadTask, _reconnectTask; | private Task _heartbeatTask, _guildDownloadTask, _reconnectTask; | ||||
| @@ -54,16 +53,17 @@ namespace Discord.WebSocket | |||||
| internal int ConnectionTimeout { get; private set; } | internal int ConnectionTimeout { get; private set; } | ||||
| internal WebSocketProvider WebSocketProvider { get; private set; } | internal WebSocketProvider WebSocketProvider { get; private set; } | ||||
| public new API.DiscordSocketApiClient ApiClient => base.ApiClient as API.DiscordSocketApiClient; | |||||
| internal SocketSelfUser CurrentUser => _currentUser as SocketSelfUser; | |||||
| public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||||
| public new SocketSelfUser CurrentUser => base.CurrentUser as SocketSelfUser; | |||||
| public IReadOnlyCollection<IPrivateChannel> PrivateChannels => DataStore.PrivateChannels; | |||||
| internal IReadOnlyCollection<SocketGuild> Guilds => DataStore.Guilds; | internal IReadOnlyCollection<SocketGuild> Guilds => DataStore.Guilds; | ||||
| internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordSocketClient(DiscordSocketConfig config) | |||||
| : base(config, CreateApiClient(config)) | |||||
| public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config)) { } | |||||
| private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client) | |||||
| : base(config, client) | |||||
| { | { | ||||
| ShardId = config.ShardId; | ShardId = config.ShardId; | ||||
| TotalShards = config.TotalShards; | TotalShards = config.TotalShards; | ||||
| @@ -72,14 +72,10 @@ namespace Discord.WebSocket | |||||
| AudioMode = config.AudioMode; | AudioMode = config.AudioMode; | ||||
| WebSocketProvider = config.WebSocketProvider; | WebSocketProvider = config.WebSocketProvider; | ||||
| ConnectionTimeout = config.ConnectionTimeout; | ConnectionTimeout = config.ConnectionTimeout; | ||||
| DataStore = new DataStore(0, 0); | DataStore = new DataStore(0, 0); | ||||
| _nextAudioId = 1; | _nextAudioId = 1; | ||||
| _gatewayLogger = LogManager.CreateLogger("Gateway"); | _gatewayLogger = LogManager.CreateLogger("Gateway"); | ||||
| #if BENCHMARK | |||||
| _benchmarkLogger = _log.CreateLogger("Benchmark"); | |||||
| #endif | |||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| _serializer.Error += (s, e) => | _serializer.Error += (s, e) => | ||||
| @@ -107,25 +103,25 @@ namespace Discord.WebSocket | |||||
| GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); | GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); | ||||
| LatencyUpdated += async (old, val) => await _gatewayLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); | LatencyUpdated += async (old, val) => await _gatewayLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); | ||||
| _voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>(); | |||||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||||
| _largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
| } | } | ||||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); | |||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, config.WebSocketProvider, requestQueue: new RequestQueue()); | |||||
| protected override async Task OnLoginAsync(TokenType tokenType, string token) | protected override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
| { | { | ||||
| var voiceRegions = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); | var voiceRegions = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); | ||||
| _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); | |||||
| _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); | |||||
| } | } | ||||
| protected override async Task OnLogoutAsync() | protected override async Task OnLogoutAsync() | ||||
| { | { | ||||
| if (ConnectionState != ConnectionState.Disconnected) | if (ConnectionState != ConnectionState.Disconnected) | ||||
| await DisconnectInternalAsync(null, false).ConfigureAwait(false); | await DisconnectInternalAsync(null, false).ConfigureAwait(false); | ||||
| _voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>(); | |||||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task ConnectAsync(bool waitForGuilds = true) | public async Task ConnectAsync(bool waitForGuilds = true) | ||||
| { | { | ||||
| @@ -319,127 +315,55 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override Task<IVoiceRegion> GetVoiceRegionAsync(string id) | |||||
| { | |||||
| VoiceRegion region; | |||||
| if (_voiceRegions.TryGetValue(id, out region)) | |||||
| return Task.FromResult<IVoiceRegion>(region); | |||||
| return Task.FromResult<IVoiceRegion>(null); | |||||
| } | |||||
| public Task<RestApplication> GetApplicationInfoAsync() | |||||
| => ClientHelper.GetApplicationInfoAsync(this); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override Task<IGuild> GetGuildAsync(ulong id) | |||||
| { | |||||
| return Task.FromResult<IGuild>(DataStore.GetGuild(id)); | |||||
| } | |||||
| public override Task<GuildEmbed?> GetGuildEmbedAsync(ulong id) | |||||
| { | |||||
| var guild = DataStore.GetGuild(id); | |||||
| if (guild != null) | |||||
| return Task.FromResult<GuildEmbed?>(new GuildEmbed(guild.IsEmbeddable, guild.EmbedChannelId)); | |||||
| else | |||||
| return Task.FromResult<GuildEmbed?>(null); | |||||
| } | |||||
| public override Task<IReadOnlyCollection<IUserGuild>> GetGuildSummariesAsync() | |||||
| { | |||||
| return Task.FromResult<IReadOnlyCollection<IUserGuild>>(Guilds); | |||||
| } | |||||
| public override Task<IReadOnlyCollection<IGuild>> GetGuildsAsync() | |||||
| public SocketGuild GetGuild(ulong id) | |||||
| { | { | ||||
| return Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | |||||
| return DataStore.GetGuild(id); | |||||
| } | } | ||||
| internal SocketGuild AddGuild(ExtendedGuild model, DataStore dataStore) | |||||
| { | |||||
| var guild = new SocketGuild(this, model, dataStore); | |||||
| dataStore.AddGuild(guild); | |||||
| if (model.Large) | |||||
| _largeGuilds.Enqueue(model.Id); | |||||
| return guild; | |||||
| } | |||||
| internal SocketGuild RemoveGuild(ulong id) | |||||
| { | |||||
| var guild = DataStore.RemoveGuild(id); | |||||
| if (guild != null) | |||||
| { | |||||
| foreach (var channel in guild.Channels) | |||||
| DataStore.RemoveChannel(id); | |||||
| foreach (var user in guild.Members) | |||||
| user.User.RemoveRef(this); | |||||
| } | |||||
| return guild; | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override Task<IChannel> GetChannelAsync(ulong id) | |||||
| { | |||||
| return Task.FromResult<IChannel>(DataStore.GetChannel(id)); | |||||
| } | |||||
| public override Task<IReadOnlyCollection<IPrivateChannel>> GetPrivateChannelsAsync() | |||||
| { | |||||
| return Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(DataStore.PrivateChannels); | |||||
| } | |||||
| internal ISocketChannel AddPrivateChannel(API.Channel model, DataStore dataStore) | |||||
| { | |||||
| switch (model.Type) | |||||
| { | |||||
| case ChannelType.DM: | |||||
| { | |||||
| var recipients = model.Recipients.Value; | |||||
| var user = GetOrAddUser(recipients[0], dataStore); | |||||
| var channel = new SocketDMChannel(this, new SocketDMUser(user), model); | |||||
| dataStore.AddChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| case ChannelType.Group: | |||||
| { | |||||
| var channel = new SocketGroupChannel(this, model); | |||||
| channel.UpdateUsers(model.Recipients.Value, dataStore); | |||||
| dataStore.AddChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| default: | |||||
| throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | |||||
| } | |||||
| } | |||||
| internal ISocketChannel RemovePrivateChannel(ulong id) | |||||
| { | |||||
| var channel = DataStore.RemoveChannel(id) as ISocketPrivateChannel; | |||||
| if (channel != null) | |||||
| { | |||||
| foreach (var recipient in channel.Recipients) | |||||
| recipient.User.RemoveRef(this); | |||||
| } | |||||
| return channel; | |||||
| } | |||||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) | |||||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override Task<IUser> GetUserAsync(ulong id) | |||||
| public IChannel GetChannel(ulong id) | |||||
| { | { | ||||
| return Task.FromResult<IUser>(DataStore.GetUser(id)); | |||||
| return DataStore.GetChannel(id); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override Task<IUser> GetUserAsync(string username, string discriminator) | |||||
| { | |||||
| return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault()); | |||||
| } | |||||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync() | |||||
| => ClientHelper.GetConnectionsAsync(this); | |||||
| /// <inheritdoc /> | |||||
| public Task<RestInvite> GetInviteAsync(string inviteId) | |||||
| => ClientHelper.GetInviteAsync(this, inviteId); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override Task<ISelfUser> GetCurrentUserAsync() | |||||
| public IUser GetUser(ulong id) | |||||
| { | { | ||||
| return Task.FromResult<ISelfUser>(_currentUser); | |||||
| return DataStore.GetUser(id); | |||||
| } | } | ||||
| internal SocketGlobalUser GetOrAddUser(API.User model, DataStore dataStore) | |||||
| /// <inheritdoc /> | |||||
| public IUser GetUser(string username, string discriminator) | |||||
| { | { | ||||
| var user = dataStore.GetOrAddUser(model.Id, _ => new SocketGlobalUser(model)); | |||||
| user.AddRef(); | |||||
| return user; | |||||
| return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); | |||||
| } | } | ||||
| internal SocketGlobalUser RemoveUser(ulong id) | |||||
| /// <inheritdoc /> | |||||
| public RestVoiceRegion GetVoiceRegion(string id) | |||||
| { | { | ||||
| return DataStore.RemoveUser(id); | |||||
| RestVoiceRegion region; | |||||
| if (_voiceRegions.TryGetValue(id, out region)) | |||||
| return region; | |||||
| return null; | |||||
| } | } | ||||
| /// <summary> Downloads the users list for all large guilds. </summary> | /// <summary> Downloads the users list for all large guilds. </summary> | ||||
| public Task DownloadAllUsersAsync() | |||||
| /*public Task DownloadAllUsersAsync() | |||||
| => DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers)); | => DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers)); | ||||
| /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | ||||
| public Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | public Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | ||||
| @@ -490,20 +414,10 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| await Task.WhenAll(batchTasks).ConfigureAwait(false); | await Task.WhenAll(batchTasks).ConfigureAwait(false); | ||||
| } | } | ||||
| } | |||||
| }*/ | |||||
| public override Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync() | |||||
| { | |||||
| return Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(_voiceRegions.ToReadOnlyCollection()); | |||||
| } | |||||
| private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | ||||
| { | { | ||||
| #if BENCHMARK | |||||
| Stopwatch stopwatch = Stopwatch.StartNew(); | |||||
| try | |||||
| { | |||||
| #endif | |||||
| if (seq != null) | if (seq != null) | ||||
| _lastSeq = seq.Value; | _lastSeq = seq.Value; | ||||
| try | try | ||||
| @@ -516,7 +430,7 @@ namespace Discord.WebSocket | |||||
| var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | ||||
| _heartbeatTime = 0; | _heartbeatTime = 0; | ||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, _clientLogger); | |||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, LogManager.ClientLogger); | |||||
| } | } | ||||
| break; | break; | ||||
| case GatewayOpCode.Heartbeat: | case GatewayOpCode.Heartbeat: | ||||
| @@ -574,9 +488,9 @@ namespace Discord.WebSocket | |||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | ||||
| var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length); | var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length); | ||||
| var currentUser = new SocketSelfUser(this, data.User); | |||||
| var currentUser = SocketSelfUser.Create(this, data.User); | |||||
| int unavailableGuilds = 0; | int unavailableGuilds = 0; | ||||
| for (int i = 0; i < data.Guilds.Length; i++) | |||||
| /*for (int i = 0; i < data.Guilds.Length; i++) | |||||
| { | { | ||||
| var model = data.Guilds[i]; | var model = data.Guilds[i]; | ||||
| var guild = AddGuild(model, dataStore); | var guild = AddGuild(model, dataStore); | ||||
| @@ -586,10 +500,10 @@ namespace Discord.WebSocket | |||||
| await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
| } | } | ||||
| for (int i = 0; i < data.PrivateChannels.Length; i++) | for (int i = 0; i < data.PrivateChannels.Length; i++) | ||||
| AddPrivateChannel(data.PrivateChannels[i], dataStore); | |||||
| AddPrivateChannel(data.PrivateChannels[i], dataStore);*/ | |||||
| _sessionId = data.SessionId; | _sessionId = data.SessionId; | ||||
| _currentUser = currentUser; | |||||
| base.CurrentUser = currentUser; | |||||
| _unavailableGuilds = unavailableGuilds; | _unavailableGuilds = unavailableGuilds; | ||||
| DataStore = dataStore; | DataStore = dataStore; | ||||
| } | } | ||||
| @@ -603,7 +517,7 @@ namespace Discord.WebSocket | |||||
| await SyncGuildsAsync().ConfigureAwait(false); | await SyncGuildsAsync().ConfigureAwait(false); | ||||
| _lastGuildAvailableTime = Environment.TickCount; | _lastGuildAvailableTime = Environment.TickCount; | ||||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger); | |||||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, LogManager.ClientLogger); | |||||
| await _readyEvent.InvokeAsync().ConfigureAwait(false); | await _readyEvent.InvokeAsync().ConfigureAwait(false); | ||||
| @@ -611,7 +525,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | ||||
| } | } | ||||
| break; | break; | ||||
| case "RESUMED": | |||||
| /*case "RESUMED": | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | ||||
| @@ -1366,7 +1280,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| before = new Presence(null, UserStatus.Offline); | |||||
| before = new SocketPresence(null, UserStatus.Offline); | |||||
| user = guild.AddOrUpdateUser(data, DataStore); | user = guild.AddOrUpdateUser(data, DataStore); | ||||
| } | } | ||||
| @@ -1430,7 +1344,7 @@ namespace Discord.WebSocket | |||||
| if (data.GuildId.HasValue) | if (data.GuildId.HasValue) | ||||
| { | { | ||||
| ISocketUser user; | ISocketUser user; | ||||
| VoiceState before, after; | |||||
| SocketVoiceState before, after; | |||||
| if (data.GuildId != null) | if (data.GuildId != null) | ||||
| { | { | ||||
| var guild = DataStore.GetGuild(data.GuildId.Value); | var guild = DataStore.GetGuild(data.GuildId.Value); | ||||
| @@ -1444,7 +1358,7 @@ namespace Discord.WebSocket | |||||
| if (data.ChannelId != null) | if (data.ChannelId != null) | ||||
| { | { | ||||
| before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); | |||||
| before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = guild.AddOrUpdateVoiceState(data, DataStore); | after = guild.AddOrUpdateVoiceState(data, DataStore); | ||||
| if (data.UserId == _currentUser.Id) | if (data.UserId == _currentUser.Id) | ||||
| { | { | ||||
| @@ -1453,8 +1367,8 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); | |||||
| after = new VoiceState(null, data); | |||||
| before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = new SocketVoiceState(null, data); | |||||
| } | } | ||||
| user = guild.GetUser(data.UserId); | user = guild.GetUser(data.UserId); | ||||
| @@ -1472,13 +1386,13 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (data.ChannelId != null) | if (data.ChannelId != null) | ||||
| { | { | ||||
| before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); | |||||
| before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = groupChannel.AddOrUpdateVoiceState(data, DataStore); | after = groupChannel.AddOrUpdateVoiceState(data, DataStore); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| before = groupChannel.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); | |||||
| after = new VoiceState(null, data); | |||||
| before = groupChannel.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); | |||||
| after = new SocketVoiceState(null, data); | |||||
| } | } | ||||
| user = groupChannel.GetUser(data.UserId); | user = groupChannel.GetUser(data.UserId); | ||||
| } | } | ||||
| @@ -1518,7 +1432,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| return; | |||||
| return;*/ | |||||
| //Ignored (User only) | //Ignored (User only) | ||||
| case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
| @@ -1550,18 +1464,9 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| #if BENCHMARK | |||||
| } | |||||
| finally | |||||
| { | |||||
| stopwatch.Stop(); | |||||
| double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | |||||
| await _benchmarkLogger.DebugAsync($"{millis} ms").ConfigureAwait(false); | |||||
| } | |||||
| #endif | |||||
| } | } | ||||
| private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, ILogger logger) | |||||
| private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, Logger logger) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -1601,7 +1506,7 @@ namespace Discord.WebSocket | |||||
| await logger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); | await logger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| private async Task WaitForGuildsAsync(CancellationToken cancelToken, ILogger logger) | |||||
| private async Task WaitForGuildsAsync(CancellationToken cancelToken, Logger logger) | |||||
| { | { | ||||
| //Wait for GUILD_AVAILABLEs | //Wait for GUILD_AVAILABLEs | ||||
| try | try | ||||
| @@ -1626,5 +1531,42 @@ namespace Discord.WebSocket | |||||
| if (guildIds.Length > 0) | if (guildIds.Length > 0) | ||||
| await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); | await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); | ||||
| } | } | ||||
| //IDiscordClient | |||||
| DiscordRestApiClient IDiscordClient.ApiClient => ApiClient; | |||||
| Task IDiscordClient.ConnectAsync() | |||||
| => ConnectAsync(); | |||||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync() | |||||
| => await GetApplicationInfoAsync().ConfigureAwait(false); | |||||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id) | |||||
| => Task.FromResult<IChannel>(GetChannel(id)); | |||||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync() | |||||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | |||||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync() | |||||
| => await GetConnectionsAsync(); | |||||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId) | |||||
| => await GetInviteAsync(inviteId); | |||||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id) | |||||
| => Task.FromResult<IGuild>(GetGuild(id)); | |||||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync() | |||||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | |||||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) | |||||
| => await CreateGuildAsync(name, region, jpegIcon); | |||||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | |||||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator) | |||||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync() | |||||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(_voiceRegions.ToReadOnlyCollection()); | |||||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id) | |||||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||||
| } | } | ||||
| } | } | ||||
| @@ -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.Collections.Immutable; | ||||
| using System.Diagnostics; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using MessageModel = Discord.API.Message; | using MessageModel = Discord.API.Message; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class DMChannel : IDMChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class SocketDMChannel : SocketChannel, IDMChannel | |||||
| { | { | ||||
| private readonly MessageManager _messages; | |||||
| private readonly MessageCache _messages; | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new SocketDMUser Recipient => base.Recipient as SocketDMUser; | |||||
| public IReadOnlyCollection<ISocketUser> Users => ImmutableArray.Create<ISocketUser>(Discord.CurrentUser, Recipient); | |||||
| IReadOnlyCollection<ISocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||||
| public SocketUser Recipient { get; private set; } | |||||
| public SocketDMChannel(DiscordSocketClient discord, SocketDMUser recipient, Model model) | |||||
| : base(discord, recipient, model) | |||||
| public IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||||
| internal SocketDMChannel(DiscordSocketClient discord, ulong id, ulong recipientId) | |||||
| : base(discord, id) | |||||
| { | { | ||||
| Recipient = new SocketUser(Discord, recipientId); | |||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| else | |||||
| _messages = new MessageManager(Discord, this); | |||||
| } | } | ||||
| internal new static SocketDMChannel Create(DiscordSocketClient discord, Model model) | |||||
| { | |||||
| var entity = new SocketDMChannel(discord, model.Id, model.Recipients.Value[0].Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| Recipient.Update(model.Recipients.Value[0]); | |||||
| } | |||||
| public Task CloseAsync() | |||||
| => ChannelHelper.DeleteAsync(this, Discord); | |||||
| public override Task<IUser> GetUserAsync(ulong id) => Task.FromResult<IUser>(GetUser(id)); | |||||
| public override Task<IReadOnlyCollection<IUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IUser>>(Users); | |||||
| public ISocketUser GetUser(ulong id) | |||||
| public SocketUser GetUser(ulong id) | |||||
| { | { | ||||
| var currentUser = Discord.CurrentUser; | |||||
| if (id == Recipient.Id) | if (id == Recipient.Id) | ||||
| return Recipient; | return Recipient; | ||||
| else if (id == currentUser.Id) | |||||
| return currentUser; | |||||
| else if (id == Discord.CurrentUser.Id) | |||||
| return Discord.CurrentUser as SocketSelfUser; | |||||
| else | else | ||||
| return null; | return null; | ||||
| } | } | ||||
| public override async Task<IMessage> GetMessageAsync(ulong id) | |||||
| { | |||||
| return await _messages.DownloadAsync(id).ConfigureAwait(false); | |||||
| } | |||||
| public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||||
| { | |||||
| return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false); | |||||
| } | |||||
| public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| { | |||||
| return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); | |||||
| } | |||||
| public ISocketMessage CreateMessage(ISocketUser author, MessageModel model) | |||||
| public SocketMessage GetMessage(ulong id) | |||||
| => _messages?.Get(id); | |||||
| public async Task<IMessage> GetMessageAsync(ulong id, bool allowDownload = true) | |||||
| { | { | ||||
| return _messages.Create(author, model); | |||||
| IMessage msg = _messages?.Get(id); | |||||
| if (msg == null && allowDownload) | |||||
| msg = await ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| return msg; | |||||
| } | } | ||||
| public ISocketMessage AddMessage(ISocketUser author, MessageModel model) | |||||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | |||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); | |||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||||
| => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); | |||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); | |||||
| public IDisposable EnterTypingState() | |||||
| => ChannelHelper.EnterTypingState(this, Discord); | |||||
| internal SocketMessage AddMessage(SocketUser author, MessageModel model) | |||||
| { | { | ||||
| var msg = _messages.Create(author, model); | |||||
| var msg = SocketMessage.Create(Discord, author, model); | |||||
| _messages.Add(msg); | _messages.Add(msg); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public ISocketMessage GetMessage(ulong id) | |||||
| { | |||||
| return _messages.Get(id); | |||||
| } | |||||
| public ISocketMessage RemoveMessage(ulong id) | |||||
| internal SocketMessage RemoveMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | return _messages.Remove(id); | ||||
| } | } | ||||
| public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | ||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); | |||||
| ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id); | |||||
| ISocketChannel ISocketChannel.Clone() => Clone(); | |||||
| public override string ToString() => $"@{Recipient}"; | |||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | |||||
| //IDMChannel | |||||
| IUser IDMChannel.Recipient => Recipient; | |||||
| //IPrivateChannel | |||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | |||||
| //IMessageChannel | |||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | |||||
| => await GetPinnedMessagesAsync().ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | |||||
| => await SendFileAsync(filePath, text, isTTS); | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||||
| => await SendFileAsync(stream, filename, text, isTTS); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS) | |||||
| => await SendMessageAsync(text, isTTS); | |||||
| IDisposable IMessageChannel.EnterTypingState() | |||||
| => EnterTypingState(); | |||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => Users; | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => GetUser(id); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,10 @@ | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | |||||
| using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using MessageModel = Discord.API.Message; | using MessageModel = Discord.API.Message; | ||||
| @@ -11,134 +14,124 @@ using VoiceStateModel = Discord.API.VoiceState; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketGroupChannel : IGroupChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class SocketGroupChannel : SocketChannel, IGroupChannel | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| private readonly MessageCache _messages; | |||||
| private readonly MessageManager _messages; | |||||
| private ConcurrentDictionary<ulong, VoiceState> _voiceStates; | |||||
| private string _iconId; | |||||
| private ConcurrentDictionary<ulong, SocketGroupUser> _users; | |||||
| private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public IReadOnlyCollection<ISocketUser> Users | |||||
| => _users.Select(x => x.Value as ISocketUser).Concat(ImmutableArray.Create(Discord.CurrentUser)).ToReadOnlyCollection(() => _users.Count + 1); | |||||
| public new IReadOnlyCollection<ISocketUser> Recipients => _users.Select(x => x.Value as ISocketUser).ToReadOnlyCollection(_users); | |||||
| public string Name { get; private set; } | |||||
| public SocketGroupChannel(DiscordSocketClient discord, Model model) | |||||
| : base(discord, model) | |||||
| public IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<SocketGroupUser> Recipients | |||||
| => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); | |||||
| internal SocketGroupChannel(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| { | { | ||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| else | |||||
| _messages = new MessageManager(Discord, this); | |||||
| _voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, 5); | |||||
| _voiceStates = new ConcurrentDictionary<ulong, SocketVoiceState>(1, 5); | |||||
| _users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, 5); | |||||
| } | } | ||||
| public override void Update(Model model) | |||||
| internal new static SocketGroupChannel Create(DiscordSocketClient discord, Model model) | |||||
| { | { | ||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| base.Update(model, source); | |||||
| var entity = new SocketGroupChannel(discord, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | } | ||||
| internal void Update(Model model) | |||||
| { | |||||
| if (model.Name.IsSpecified) | |||||
| Name = model.Name.Value; | |||||
| if (model.Icon.IsSpecified) | |||||
| _iconId = model.Icon.Value; | |||||
| internal void UpdateUsers(UserModel[] models, DataStore dataStore) | |||||
| if (model.Recipients.IsSpecified) | |||||
| UpdateUsers(model.Recipients.Value); | |||||
| } | |||||
| internal virtual void UpdateUsers(API.User[] models) | |||||
| { | { | ||||
| var users = new ConcurrentDictionary<ulong, GroupUser>(1, models.Length); | |||||
| var users = new ConcurrentDictionary<ulong, SocketGroupUser>(1, (int)(models.Length * 1.05)); | |||||
| for (int i = 0; i < models.Length; i++) | for (int i = 0; i < models.Length; i++) | ||||
| { | |||||
| var globalUser = Discord.GetOrAddUser(models[i], dataStore); | |||||
| users[models[i].Id] = new SocketGroupUser(this, globalUser); | |||||
| } | |||||
| users[models[i].Id] = SocketGroupUser.Create(Discord, models[i]); | |||||
| _users = users; | _users = users; | ||||
| } | } | ||||
| internal override void UpdateUsers(UserModel[] models) | |||||
| => UpdateUsers(models, source, Discord.DataStore); | |||||
| public SocketGroupUser AddUser(UserModel model, DataStore dataStore) | |||||
| { | |||||
| GroupUser user; | |||||
| if (_users.TryGetValue(model.Id, out user)) | |||||
| return user as SocketGroupUser; | |||||
| else | |||||
| { | |||||
| var globalUser = Discord.GetOrAddUser(model, dataStore); | |||||
| var privateUser = new SocketGroupUser(this, globalUser); | |||||
| _users[privateUser.Id] = privateUser; | |||||
| return privateUser; | |||||
| } | |||||
| } | |||||
| public ISocketUser GetUser(ulong id) | |||||
| public async Task UpdateAsync() | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | |||||
| public Task LeaveAsync() | |||||
| => ChannelHelper.DeleteAsync(this, Discord); | |||||
| public SocketGroupUser GetUser(ulong id) | |||||
| { | { | ||||
| GroupUser user; | |||||
| SocketGroupUser user; | |||||
| if (_users.TryGetValue(id, out user)) | if (_users.TryGetValue(id, out user)) | ||||
| return user as SocketGroupUser; | |||||
| if (id == Discord.CurrentUser.Id) | |||||
| return Discord.CurrentUser; | |||||
| return null; | |||||
| } | |||||
| public SocketGroupUser RemoveUser(ulong id) | |||||
| { | |||||
| GroupUser user; | |||||
| if (_users.TryRemove(id, out user)) | |||||
| return user as SocketGroupUser; | |||||
| return user; | |||||
| return null; | return null; | ||||
| } | } | ||||
| public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | |||||
| { | |||||
| var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; | |||||
| var voiceState = new VoiceState(voiceChannel, model); | |||||
| (voiceStates ?? _voiceStates)[model.UserId] = voiceState; | |||||
| return voiceState; | |||||
| } | |||||
| public VoiceState? GetVoiceState(ulong id) | |||||
| { | |||||
| VoiceState voiceState; | |||||
| if (_voiceStates.TryGetValue(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| public VoiceState? RemoveVoiceState(ulong id) | |||||
| { | |||||
| VoiceState voiceState; | |||||
| if (_voiceStates.TryRemove(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| public Task<RestMessage> GetMessageAsync(ulong id) | |||||
| => ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | |||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | |||||
| public override async Task<IMessage> GetMessageAsync(ulong id) | |||||
| { | |||||
| return await _messages.DownloadAsync(id).ConfigureAwait(false); | |||||
| } | |||||
| public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||||
| { | |||||
| return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false); | |||||
| } | |||||
| public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| { | |||||
| return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); | |||||
| } | |||||
| public ISocketMessage CreateMessage(ISocketUser author, MessageModel model) | |||||
| { | |||||
| return _messages.Create(author, model); | |||||
| } | |||||
| public ISocketMessage AddMessage(ISocketUser author, MessageModel model) | |||||
| { | |||||
| var msg = _messages.Create(author, model); | |||||
| _messages.Add(msg); | |||||
| return msg; | |||||
| } | |||||
| public ISocketMessage GetMessage(ulong id) | |||||
| { | |||||
| return _messages.Get(id); | |||||
| } | |||||
| public ISocketMessage RemoveMessage(ulong id) | |||||
| { | |||||
| return _messages.Remove(id); | |||||
| } | |||||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); | |||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||||
| => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); | |||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); | |||||
| public IDisposable EnterTypingState() | |||||
| => ChannelHelper.EnterTypingState(this, Discord); | |||||
| //IPrivateChannel | |||||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | |||||
| //IMessageChannel | |||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) | |||||
| => null; | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | |||||
| => await GetPinnedMessagesAsync(); | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | |||||
| => await SendFileAsync(filePath, text, isTTS); | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||||
| => await SendFileAsync(stream, filename, text, isTTS); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS) | |||||
| => await SendMessageAsync(text, isTTS); | |||||
| IDisposable IMessageChannel.EnterTypingState() | |||||
| => EnterTypingState(); | |||||
| public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; | |||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers => Users; | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); | |||||
| ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id); | |||||
| ISocketChannel ISocketChannel.Clone() => Clone(); | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => GetUser(id); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(GetUser(id)); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Discord.Rest; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| @@ -7,89 +8,59 @@ using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| namespace Discord.Rest | |||||
| namespace Discord.WebSocket | |||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| internal abstract class SocketGuildChannel : ISnowflakeEntity, IGuildChannel | |||||
| public abstract class SocketGuildChannel : SocketChannel, IGuildChannel | |||||
| { | { | ||||
| private List<Overwrite> _overwrites; //TODO: Is maintaining a list here too expensive? Is this threadsafe? | |||||
| private ImmutableArray<Overwrite> _overwrites; | |||||
| public string Name { get; private set; } | |||||
| public int Position { get; private set; } | |||||
| public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | |||||
| public Guild Guild { get; private set; } | |||||
| public ulong GuildId { get; } | |||||
| public override DiscordRestClient Discord => Guild.Discord; | |||||
| public string Name { get; private set; } | |||||
| public int Position { get; private set; } | |||||
| public GuildChannel(Guild guild, Model model) | |||||
| : base(model.Id) | |||||
| internal SocketGuildChannel(DiscordSocketClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id) | |||||
| { | { | ||||
| Guild = guild; | |||||
| Update(model); | |||||
| GuildId = guildId; | |||||
| } | } | ||||
| public virtual void Update(Model model) | |||||
| internal new static SocketGuildChannel Create(DiscordSocketClient discord, Model model) | |||||
| { | |||||
| switch (model.Type) | |||||
| { | |||||
| case ChannelType.Text: | |||||
| return SocketTextChannel.Create(discord, model); | |||||
| case ChannelType.Voice: | |||||
| return SocketVoiceChannel.Create(discord, model); | |||||
| default: | |||||
| throw new InvalidOperationException("Unknown guild channel type"); | |||||
| } | |||||
| } | |||||
| internal virtual void Update(Model model) | |||||
| { | { | ||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| Name = model.Name.Value; | Name = model.Name.Value; | ||||
| Position = model.Position.Value; | Position = model.Position.Value; | ||||
| var overwrites = model.PermissionOverwrites.Value; | var overwrites = model.PermissionOverwrites.Value; | ||||
| var newOverwrites = new List<Overwrite>(overwrites.Length); | |||||
| var newOverwrites = ImmutableArray.CreateBuilder<Overwrite>(overwrites.Length); | |||||
| for (int i = 0; i < overwrites.Length; i++) | for (int i = 0; i < overwrites.Length; i++) | ||||
| newOverwrites.Add(new Overwrite(overwrites[i])); | newOverwrites.Add(new Overwrite(overwrites[i])); | ||||
| _overwrites = newOverwrites; | |||||
| _overwrites = newOverwrites.ToImmutable(); | |||||
| } | } | ||||
| public async Task UpdateAsync() | public async Task UpdateAsync() | ||||
| { | |||||
| if (IsAttached) throw new NotSupportedException(); | |||||
| var model = await Discord.ApiClient.GetChannelAsync(Id).ConfigureAwait(false); | |||||
| Update(model, UpdateSource.Rest); | |||||
| } | |||||
| public async Task ModifyAsync(Action<ModifyGuildChannelParams> func) | |||||
| { | |||||
| if (func == null) throw new NullReferenceException(nameof(func)); | |||||
| var args = new ModifyGuildChannelParams(); | |||||
| func(args); | |||||
| if (!args._name.IsSpecified) | |||||
| args._name = Name; | |||||
| var model = await Discord.ApiClient.ModifyGuildChannelAsync(Id, args).ConfigureAwait(false); | |||||
| Update(model, UpdateSource.Rest); | |||||
| } | |||||
| public async Task DeleteAsync() | |||||
| { | |||||
| await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false); | |||||
| } | |||||
| public abstract Task<IGuildUser> GetUserAsync(ulong id); | |||||
| public abstract Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(); | |||||
| public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync() | |||||
| { | |||||
| var models = await Discord.ApiClient.GetChannelInvitesAsync(Id).ConfigureAwait(false); | |||||
| return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray(); | |||||
| } | |||||
| public async Task<IInviteMetadata> CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) | |||||
| { | |||||
| var args = new CreateChannelInviteParams | |||||
| { | |||||
| MaxAge = maxAge ?? 0, | |||||
| MaxUses = maxUses ?? 0, | |||||
| Temporary = isTemporary | |||||
| }; | |||||
| var model = await Discord.ApiClient.CreateChannelInviteAsync(Id, args).ConfigureAwait(false); | |||||
| return new InviteMetadata(Discord, model); | |||||
| } | |||||
| => Update(await ChannelHelper.GetAsync(this, Discord)); | |||||
| public Task ModifyAsync(Action<ModifyGuildChannelParams> func) | |||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | |||||
| public Task DeleteAsync() | |||||
| => ChannelHelper.DeleteAsync(this, Discord); | |||||
| public OverwritePermissions? GetPermissionOverwrite(IUser user) | public OverwritePermissions? GetPermissionOverwrite(IUser user) | ||||
| { | { | ||||
| for (int i = 0; i < _overwrites.Count; i++) | |||||
| for (int i = 0; i < _overwrites.Length; i++) | |||||
| { | { | ||||
| if (_overwrites[i].TargetId == user.Id) | if (_overwrites[i].TargetId == user.Id) | ||||
| return _overwrites[i].Permissions; | return _overwrites[i].Permissions; | ||||
| @@ -98,60 +69,91 @@ namespace Discord.Rest | |||||
| } | } | ||||
| public OverwritePermissions? GetPermissionOverwrite(IRole role) | public OverwritePermissions? GetPermissionOverwrite(IRole role) | ||||
| { | { | ||||
| for (int i = 0; i < _overwrites.Count; i++) | |||||
| for (int i = 0; i < _overwrites.Length; i++) | |||||
| { | { | ||||
| if (_overwrites[i].TargetId == role.Id) | if (_overwrites[i].TargetId == role.Id) | ||||
| return _overwrites[i].Permissions; | return _overwrites[i].Permissions; | ||||
| } | } | ||||
| return null; | return null; | ||||
| } | } | ||||
| public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms) | public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "member" }; | |||||
| await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, user.Id, args).ConfigureAwait(false); | |||||
| _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); | |||||
| await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms).ConfigureAwait(false); | |||||
| _overwrites = _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); | |||||
| } | } | ||||
| public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) | public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) | ||||
| { | { | ||||
| var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "role" }; | |||||
| await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, role.Id, args).ConfigureAwait(false); | |||||
| await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms).ConfigureAwait(false); | |||||
| _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); | _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); | ||||
| } | } | ||||
| public async Task RemovePermissionOverwriteAsync(IUser user) | public async Task RemovePermissionOverwriteAsync(IUser user) | ||||
| { | { | ||||
| await Discord.ApiClient.DeleteChannelPermissionAsync(Id, user.Id).ConfigureAwait(false); | |||||
| await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user).ConfigureAwait(false); | |||||
| for (int i = 0; i < _overwrites.Count; i++) | |||||
| for (int i = 0; i < _overwrites.Length; i++) | |||||
| { | { | ||||
| if (_overwrites[i].TargetId == user.Id) | if (_overwrites[i].TargetId == user.Id) | ||||
| { | { | ||||
| _overwrites.RemoveAt(i); | |||||
| _overwrites = _overwrites.RemoveAt(i); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public async Task RemovePermissionOverwriteAsync(IRole role) | public async Task RemovePermissionOverwriteAsync(IRole role) | ||||
| { | { | ||||
| await Discord.ApiClient.DeleteChannelPermissionAsync(Id, role.Id).ConfigureAwait(false); | |||||
| await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role).ConfigureAwait(false); | |||||
| for (int i = 0; i < _overwrites.Count; i++) | |||||
| for (int i = 0; i < _overwrites.Length; i++) | |||||
| { | { | ||||
| if (_overwrites[i].TargetId == role.Id) | if (_overwrites[i].TargetId == role.Id) | ||||
| { | { | ||||
| _overwrites.RemoveAt(i); | |||||
| _overwrites = _overwrites.RemoveAt(i); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||||
| IGuild IGuildChannel.Guild => Guild; | |||||
| IReadOnlyCollection<Overwrite> IGuildChannel.PermissionOverwrites => _overwrites.AsReadOnly(); | |||||
| async Task<IUser> IChannel.GetUserAsync(ulong id) => await GetUserAsync(id).ConfigureAwait(false); | |||||
| async Task<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() => await GetUsersAsync().ConfigureAwait(false); | |||||
| public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync() | |||||
| => await ChannelHelper.GetInvitesAsync(this, Discord); | |||||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) | |||||
| => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); | |||||
| //IGuildChannel | |||||
| async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync() | |||||
| => await GetInvitesAsync(); | |||||
| async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) | |||||
| => await CreateInviteAsync(maxAge, maxUses, isTemporary); | |||||
| OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) | |||||
| => GetPermissionOverwrite(role); | |||||
| OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) | |||||
| => GetPermissionOverwrite(user); | |||||
| async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions) | |||||
| => await AddPermissionOverwriteAsync(role, permissions); | |||||
| async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions) | |||||
| => await AddPermissionOverwriteAsync(user, permissions); | |||||
| async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role) | |||||
| => await RemovePermissionOverwriteAsync(role); | |||||
| async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) | |||||
| => await RemovePermissionOverwriteAsync(user); | |||||
| IReadOnlyCollection<IGuildUser> IGuildChannel.CachedUsers | |||||
| => ImmutableArray.Create<IGuildUser>(); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| IGuildUser IGuildChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| //IChannel | |||||
| IReadOnlyCollection<IUser> IChannel.CachedUsers | |||||
| => ImmutableArray.Create<IUser>(); | |||||
| IUser IChannel.GetCachedUser(ulong id) | |||||
| => null; | |||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IUser>(null); //Overriden in Text/Voice //TODO: Does this actually override? | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,10 @@ | |||||
| using Discord.Rest; | |||||
| using Discord.API.Rest; | |||||
| using Discord.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | |||||
| using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using MessageModel = Discord.API.Message; | using MessageModel = Discord.API.Message; | ||||
| @@ -8,81 +12,105 @@ using Model = Discord.API.Channel; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketTextChannel : TextChannel, ISocketGuildChannel, ISocketMessageChannel | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class SocketTextChannel : SocketGuildChannel, ITextChannel | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| private readonly MessageCache _messages; | |||||
| private readonly MessageManager _messages; | |||||
| public string Topic { get; private set; } | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new SocketGuild Guild => base.Guild as SocketGuild; | |||||
| public string Mention => MentionUtils.MentionChannel(Id); | |||||
| public IReadOnlyCollection<SocketGuildUser> Members | |||||
| => Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray(); | |||||
| public SocketTextChannel(SocketGuild guild, Model model) | |||||
| : base(guild, model) | |||||
| internal SocketTextChannel(DiscordSocketClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id, guildId) | |||||
| { | { | ||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| else | |||||
| _messages = new MessageManager(Discord, this); | |||||
| } | |||||
| public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members); | |||||
| public SocketGuildUser GetUser(ulong id, bool skipCheck = false) | |||||
| { | |||||
| var user = Guild.GetUser(id); | |||||
| if (skipCheck) return user; | |||||
| if (user != null) | |||||
| { | |||||
| ulong perms = Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue); | |||||
| if (Permissions.GetValue(perms, ChannelPermission.ReadMessages)) | |||||
| return user; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| public override async Task<IMessage> GetMessageAsync(ulong id) | |||||
| { | |||||
| return await _messages.DownloadAsync(id).ConfigureAwait(false); | |||||
| } | } | ||||
| public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| internal new static SocketTextChannel Create(DiscordSocketClient discord, Model model) | |||||
| { | { | ||||
| return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false); | |||||
| var entity = new SocketTextChannel(discord, model.Id, model.GuildId.Value); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | } | ||||
| public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| internal override void Update(Model model) | |||||
| { | { | ||||
| return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); | |||||
| } | |||||
| base.Update(model); | |||||
| public ISocketMessage CreateMessage(ISocketUser author, MessageModel model) | |||||
| { | |||||
| return _messages.Create(author, model); | |||||
| Topic = model.Topic.Value; | |||||
| } | } | ||||
| public ISocketMessage AddMessage(ISocketUser author, MessageModel model) | |||||
| public Task ModifyAsync(Action<ModifyTextChannelParams> func) | |||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | |||||
| public Task<RestGuildUser> GetUserAsync(ulong id) | |||||
| => ChannelHelper.GetUserAsync(this, Discord, id); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync() | |||||
| => ChannelHelper.GetUsersAsync(this, Discord); | |||||
| public Task<RestMessage> GetMessageAsync(ulong id) | |||||
| => ChannelHelper.GetMessageAsync(this, Discord, id); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); | |||||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync() | |||||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); | |||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||||
| => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); | |||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); | |||||
| public IDisposable EnterTypingState() | |||||
| => ChannelHelper.EnterTypingState(this, Discord); | |||||
| internal SocketMessage AddMessage(SocketUser author, MessageModel model) | |||||
| { | { | ||||
| var msg = _messages.Create(author, model); | |||||
| var msg = SocketMessage.Create(Discord, author, model); | |||||
| _messages.Add(msg); | _messages.Add(msg); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public ISocketMessage GetMessage(ulong id) | |||||
| { | |||||
| return _messages.Get(id); | |||||
| } | |||||
| public ISocketMessage RemoveMessage(ulong id) | |||||
| internal SocketMessage RemoveMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | return _messages.Remove(id); | ||||
| } | } | ||||
| public SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"@{Name} ({Id}, Text)"; | |||||
| //IGuildChannel | |||||
| async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => await GetUserAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => GetUsersAsync(); | |||||
| IReadOnlyCollection<ISocketUser> ISocketMessageChannel.Users => Members; | |||||
| //IMessageChannel | |||||
| IReadOnlyCollection<IMessage> IMessageChannel.CachedMessages => ImmutableArray.Create<IMessage>(); | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | |||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); | |||||
| ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id, skipCheck); | |||||
| ISocketChannel ISocketChannel.Clone() => Clone(); | |||||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id) | |||||
| => await GetMessageAsync(id); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit) | |||||
| => GetMessagesAsync(limit); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||||
| => GetMessagesAsync(fromMessageId, dir, limit); | |||||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync() | |||||
| => await GetPinnedMessagesAsync().ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) | |||||
| => await SendFileAsync(filePath, text, isTTS); | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||||
| => await SendFileAsync(stream, filename, text, isTTS); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS) | |||||
| => await SendMessageAsync(text, isTTS); | |||||
| IDisposable IMessageChannel.EnterTypingState() | |||||
| => EnterTypingState(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,54 +1,50 @@ | |||||
| using Discord.Audio; | |||||
| using Discord.API.Rest; | |||||
| using Discord.Audio; | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketVoiceChannel : VoiceChannel, ISocketGuildChannel | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| public int Bitrate { get; private set; } | |||||
| public int UserLimit { get; private set; } | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new SocketGuild Guild => base.Guild as SocketGuild; | |||||
| public IReadOnlyCollection<IGuildUser> Members | |||||
| => Guild.VoiceStates.Where(x => x.Value.VoiceChannel.Id == Id).Select(x => Guild.GetUser(x.Key)).ToImmutableArray(); | |||||
| public SocketVoiceChannel(SocketGuild guild, Model model) | |||||
| : base(guild, model) | |||||
| internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, ulong guildId) | |||||
| : base(discord, id, guildId) | |||||
| { | { | ||||
| } | } | ||||
| public override Task<IGuildUser> GetUserAsync(ulong id) | |||||
| => Task.FromResult(GetUser(id)); | |||||
| public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() | |||||
| => Task.FromResult(Members); | |||||
| public IGuildUser GetUser(ulong id) | |||||
| internal new static SocketVoiceChannel Create(DiscordSocketClient discord, Model model) | |||||
| { | { | ||||
| var user = Guild.GetUser(id); | |||||
| if (user != null && user.VoiceChannel.Id == Id) | |||||
| return user; | |||||
| return null; | |||||
| var entity = new SocketVoiceChannel(discord, model.Id, model.GuildId.Value); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | } | ||||
| public override async Task<IAudioClient> ConnectAsync() | |||||
| internal override void Update(Model model) | |||||
| { | { | ||||
| var audioMode = Discord.AudioMode; | |||||
| if (audioMode == AudioMode.Disabled) | |||||
| throw new InvalidOperationException($"Audio is not enabled on this client, {nameof(DiscordSocketConfig.AudioMode)} in {nameof(DiscordSocketConfig)} must be set."); | |||||
| return await Guild.ConnectAudioAsync(Id, | |||||
| (audioMode & AudioMode.Incoming) == 0, | |||||
| (audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false); | |||||
| base.Update(model); | |||||
| Bitrate = model.Bitrate.Value; | |||||
| UserLimit = model.UserLimit.Value; | |||||
| } | } | ||||
| public SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; | |||||
| public Task ModifyAsync(Action<ModifyVoiceChannelParams> func) | |||||
| => ChannelHelper.ModifyAsync(this, Discord, func); | |||||
| //IVoiceChannel | |||||
| Task<IAudioClient> IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } | |||||
| ISocketChannel ISocketChannel.Clone() => Clone(); | |||||
| //IGuildChannel | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id) | |||||
| => Task.FromResult<IGuildUser>(null); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync() | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>().ToAsyncEnumerable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Audio; | |||||
| using Discord.API.Rest; | |||||
| using Discord.Audio; | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| @@ -19,402 +20,204 @@ using VoiceStateModel = Discord.API.VoiceState; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketGuild : Guild, IGuild, IUserGuild | |||||
| public class SocketGuild : SocketEntity<ulong>, IGuild | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| private readonly SemaphoreSlim _audioLock; | |||||
| private TaskCompletionSource<bool> _syncPromise, _downloaderPromise; | |||||
| private TaskCompletionSource<AudioClient> _audioConnectPromise; | |||||
| private ConcurrentHashSet<ulong> _channels; | |||||
| private ConcurrentDictionary<ulong, SocketGuildUser> _members; | |||||
| private ConcurrentDictionary<ulong, VoiceState> _voiceStates; | |||||
| private ImmutableDictionary<ulong, RestRole> _roles; | |||||
| private ImmutableArray<Emoji> _emojis; | |||||
| private ImmutableArray<string> _features; | |||||
| internal bool _available; | internal bool _available; | ||||
| public bool Available => _available && Discord.ConnectionState == ConnectionState.Connected; | |||||
| public int MemberCount { get; set; } | |||||
| public int DownloadedMemberCount { get; private set; } | |||||
| public AudioClient AudioClient { get; private set; } | |||||
| public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | |||||
| public bool IsSynced => _syncPromise.Task.IsCompleted; | |||||
| public Task SyncPromise => _syncPromise.Task; | |||||
| public Task DownloaderPromise => _downloaderPromise.Task; | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public SocketGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id); | |||||
| public IReadOnlyCollection<ISocketGuildChannel> Channels | |||||
| { | |||||
| get | |||||
| { | |||||
| var channels = _channels; | |||||
| var store = Discord.DataStore; | |||||
| return channels.Select(x => store.GetChannel(x) as ISocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); | |||||
| } | |||||
| } | |||||
| public IReadOnlyCollection<SocketGuildUser> Members => _members.ToReadOnlyCollection(); | |||||
| public IEnumerable<KeyValuePair<ulong, VoiceState>> VoiceStates => _voiceStates; | |||||
| public SocketGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) | |||||
| { | |||||
| _audioLock = new SemaphoreSlim(1, 1); | |||||
| _syncPromise = new TaskCompletionSource<bool>(); | |||||
| _downloaderPromise = new TaskCompletionSource<bool>(); | |||||
| Update(model, dataStore); | |||||
| } | |||||
| public void Update(ExtendedModel model, DataStore dataStore) | |||||
| { | |||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| _available = !(model.Unavailable ?? false); | |||||
| if (!_available) | |||||
| { | |||||
| if (_channels == null) | |||||
| _channels = new ConcurrentHashSet<ulong>(); | |||||
| if (_members == null) | |||||
| _members = new ConcurrentDictionary<ulong, SocketGuildUser>(); | |||||
| if (_roles == null) | |||||
| _roles = new ConcurrentDictionary<ulong, Role>(); | |||||
| if (Emojis == null) | |||||
| Emojis = ImmutableArray.Create<Emoji>(); | |||||
| if (Features == null) | |||||
| Features = ImmutableArray.Create<string>(); | |||||
| return; | |||||
| } | |||||
| base.Update(model as Model, source); | |||||
| var channels = new ConcurrentHashSet<ulong>(1, (int)(model.Channels.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.Channels.Length; i++) | |||||
| AddChannel(model.Channels[i], dataStore, channels); | |||||
| } | |||||
| _channels = channels; | |||||
| var members = new ConcurrentDictionary<ulong, SocketGuildUser>(1, (int)(model.Presences.Length * 1.05)); | |||||
| { | |||||
| DownloadedMemberCount = 0; | |||||
| for (int i = 0; i < model.Members.Length; i++) | |||||
| AddOrUpdateUser(model.Members[i], dataStore, members); | |||||
| if (Discord.ApiClient.AuthTokenType != TokenType.User) | |||||
| { | |||||
| var _ = _syncPromise.TrySetResultAsync(true); | |||||
| if (!model.Large) | |||||
| _ = _downloaderPromise.TrySetResultAsync(true); | |||||
| } | |||||
| for (int i = 0; i < model.Presences.Length; i++) | |||||
| AddOrUpdateUser(model.Presences[i], dataStore, members); | |||||
| } | |||||
| _members = members; | |||||
| MemberCount = model.MemberCount; | |||||
| var voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, (int)(model.VoiceStates.Length * 1.05)); | |||||
| { | |||||
| for (int i = 0; i < model.VoiceStates.Length; i++) | |||||
| AddOrUpdateVoiceState(model.VoiceStates[i], dataStore, voiceStates); | |||||
| } | |||||
| _voiceStates = voiceStates; | |||||
| } | |||||
| public void Update(GuildSyncModel model, DataStore dataStore) | |||||
| { | |||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| var members = new ConcurrentDictionary<ulong, SocketGuildUser>(1, (int)(model.Presences.Length * 1.05)); | |||||
| { | |||||
| DownloadedMemberCount = 0; | |||||
| for (int i = 0; i < model.Members.Length; i++) | |||||
| AddOrUpdateUser(model.Members[i], dataStore, members); | |||||
| var _ = _syncPromise.TrySetResultAsync(true); | |||||
| if (!model.Large) | |||||
| _ = _downloaderPromise.TrySetResultAsync(true); | |||||
| for (int i = 0; i < model.Presences.Length; i++) | |||||
| AddOrUpdateUser(model.Presences[i], dataStore, members); | |||||
| public string Name { get; private set; } | |||||
| public int AFKTimeout { get; private set; } | |||||
| public bool IsEmbeddable { get; private set; } | |||||
| public VerificationLevel VerificationLevel { get; private set; } | |||||
| public MfaLevel MfaLevel { get; private set; } | |||||
| public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } | |||||
| public ulong? AFKChannelId { get; private set; } | |||||
| public ulong? EmbedChannelId { get; private set; } | |||||
| public ulong OwnerId { get; private set; } | |||||
| public string VoiceRegionId { get; private set; } | |||||
| public string IconId { get; private set; } | |||||
| public string SplashId { get; private set; } | |||||
| public ulong DefaultChannelId => Id; | |||||
| public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); | |||||
| public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); | |||||
| public bool IsSynced => false; | |||||
| public RestRole EveryoneRole => GetRole(Id); | |||||
| public IReadOnlyCollection<RestRole> Roles => _roles.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<Emoji> Emojis => _emojis; | |||||
| public IReadOnlyCollection<string> Features => _features; | |||||
| internal SocketGuild(DiscordSocketClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| } | |||||
| internal static SocketGuild Create(DiscordSocketClient discord, Model model) | |||||
| { | |||||
| var entity = new SocketGuild(discord, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| AFKChannelId = model.AFKChannelId; | |||||
| EmbedChannelId = model.EmbedChannelId; | |||||
| AFKTimeout = model.AFKTimeout; | |||||
| IsEmbeddable = model.EmbedEnabled; | |||||
| IconId = model.Icon; | |||||
| Name = model.Name; | |||||
| OwnerId = model.OwnerId; | |||||
| VoiceRegionId = model.Region; | |||||
| SplashId = model.Splash; | |||||
| VerificationLevel = model.VerificationLevel; | |||||
| MfaLevel = model.MfaLevel; | |||||
| DefaultMessageNotifications = model.DefaultMessageNotifications; | |||||
| if (model.Emojis != null) | |||||
| { | |||||
| var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | |||||
| for (int i = 0; i < model.Emojis.Length; i++) | |||||
| emojis.Add(Emoji.Create(model.Emojis[i])); | |||||
| _emojis = emojis.ToImmutableArray(); | |||||
| } | } | ||||
| _members = members; | |||||
| } | |||||
| public void Update(EmojiUpdateModel model) | |||||
| { | |||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | |||||
| for (int i = 0; i < model.Emojis.Length; i++) | |||||
| emojis.Add(new Emoji(model.Emojis[i])); | |||||
| Emojis = emojis.ToImmutableArray(); | |||||
| } | |||||
| public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id)); | |||||
| public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); | |||||
| public ISocketGuildChannel AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null) | |||||
| { | |||||
| var channel = ToChannel(model); | |||||
| (channels ?? _channels).TryAdd(model.Id); | |||||
| dataStore.AddChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| public ISocketGuildChannel GetChannel(ulong id) | |||||
| { | |||||
| return Discord.DataStore.GetChannel(id) as ISocketGuildChannel; | |||||
| } | |||||
| public ISocketGuildChannel RemoveChannel(ulong id) | |||||
| { | |||||
| _channels.TryRemove(id); | |||||
| return Discord.DataStore.RemoveChannel(id) as ISocketGuildChannel; | |||||
| } | |||||
| public Role AddRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null) | |||||
| { | |||||
| var role = new Role(this, model); | |||||
| (roles ?? _roles)[model.Id] = role; | |||||
| return role; | |||||
| } | |||||
| public Role RemoveRole(ulong id) | |||||
| { | |||||
| Role role; | |||||
| if (_roles.TryRemove(id, out role)) | |||||
| return role; | |||||
| return null; | |||||
| } | |||||
| public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| public override Task<IGuildUser> GetCurrentUserAsync() | |||||
| => Task.FromResult<IGuildUser>(CurrentUser); | |||||
| public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() | |||||
| => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members); | |||||
| public SocketGuildUser AddOrUpdateUser(MemberModel model, DataStore dataStore, ConcurrentDictionary<ulong, SocketGuildUser> members = null) | |||||
| { | |||||
| members = members ?? _members; | |||||
| SocketGuildUser member; | |||||
| if (members.TryGetValue(model.User.Id, out member)) | |||||
| member.Update(model, UpdateSource.WebSocket); | |||||
| else | else | ||||
| { | |||||
| var user = Discord.GetOrAddUser(model.User, dataStore); | |||||
| member = new SocketGuildUser(this, user, model); | |||||
| members[user.Id] = member; | |||||
| DownloadedMemberCount++; | |||||
| } | |||||
| return member; | |||||
| } | |||||
| public SocketGuildUser AddOrUpdateUser(PresenceModel model, DataStore dataStore, ConcurrentDictionary<ulong, SocketGuildUser> members = null) | |||||
| { | |||||
| members = members ?? _members; | |||||
| _emojis = ImmutableArray.Create<Emoji>(); | |||||
| SocketGuildUser member; | |||||
| if (members.TryGetValue(model.User.Id, out member)) | |||||
| member.Update(model, UpdateSource.WebSocket); | |||||
| if (model.Features != null) | |||||
| _features = model.Features.ToImmutableArray(); | |||||
| else | else | ||||
| { | |||||
| var user = Discord.GetOrAddUser(model.User, dataStore); | |||||
| member = new SocketGuildUser(this, user, model); | |||||
| members[user.Id] = member; | |||||
| DownloadedMemberCount++; | |||||
| } | |||||
| return member; | |||||
| } | |||||
| public SocketGuildUser GetUser(ulong id) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryGetValue(id, out member)) | |||||
| return member; | |||||
| return null; | |||||
| } | |||||
| public SocketGuildUser RemoveUser(ulong id) | |||||
| { | |||||
| SocketGuildUser member; | |||||
| if (_members.TryRemove(id, out member)) | |||||
| { | |||||
| DownloadedMemberCount--; | |||||
| return member; | |||||
| } | |||||
| member.User.RemoveRef(Discord); | |||||
| _features = ImmutableArray.Create<string>(); | |||||
| var roles = ImmutableDictionary.CreateBuilder<ulong, RestRole>(); | |||||
| if (model.Roles != null) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| _roles = roles.ToImmutable(); | |||||
| } | |||||
| //General | |||||
| public async Task UpdateAsync() | |||||
| => Update(await Discord.ApiClient.GetGuildAsync(Id)); | |||||
| public Task DeleteAsync() | |||||
| => GuildHelper.DeleteAsync(this, Discord); | |||||
| public Task ModifyAsync(Action<ModifyGuildParams> func) | |||||
| => GuildHelper.ModifyAsync(this, Discord, func); | |||||
| public Task ModifyEmbedAsync(Action<ModifyGuildEmbedParams> func) | |||||
| => GuildHelper.ModifyEmbedAsync(this, Discord, func); | |||||
| public Task ModifyChannelsAsync(IEnumerable<ModifyGuildChannelsParams> args) | |||||
| => GuildHelper.ModifyChannelsAsync(this, Discord, args); | |||||
| public Task ModifyRolesAsync(IEnumerable<ModifyGuildRolesParams> args) | |||||
| => GuildHelper.ModifyRolesAsync(this, Discord, args); | |||||
| public Task LeaveAsync() | |||||
| => GuildHelper.LeaveAsync(this, Discord); | |||||
| //Bans | |||||
| public Task<IReadOnlyCollection<RestBan>> GetBansAsync() | |||||
| => GuildHelper.GetBansAsync(this, Discord); | |||||
| public Task AddBanAsync(IUser user, int pruneDays = 0) | |||||
| => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays); | |||||
| public Task AddBanAsync(ulong userId, int pruneDays = 0) | |||||
| => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays); | |||||
| public Task RemoveBanAsync(IUser user) | |||||
| => GuildHelper.RemoveBanAsync(this, Discord, user.Id); | |||||
| public Task RemoveBanAsync(ulong userId) | |||||
| => GuildHelper.RemoveBanAsync(this, Discord, userId); | |||||
| //Channels | |||||
| public Task<IReadOnlyCollection<RestGuildChannel>> GetChannelsAsync() | |||||
| => GuildHelper.GetChannelsAsync(this, Discord); | |||||
| public Task<RestGuildChannel> GetChannelAsync(ulong id) | |||||
| => GuildHelper.GetChannelAsync(this, Discord, id); | |||||
| public Task<RestTextChannel> CreateTextChannelAsync(string name) | |||||
| => GuildHelper.CreateTextChannelAsync(this, Discord, name); | |||||
| public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name) | |||||
| => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); | |||||
| //Integrations | |||||
| public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync() | |||||
| => GuildHelper.GetIntegrationsAsync(this, Discord); | |||||
| public Task<RestGuildIntegration> CreateIntegrationAsync(ulong id, string type) | |||||
| => GuildHelper.CreateIntegrationAsync(this, Discord, id, type); | |||||
| //Invites | |||||
| public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync() | |||||
| => GuildHelper.GetInvitesAsync(this, Discord); | |||||
| //Roles | |||||
| public RestRole GetRole(ulong id) | |||||
| { | |||||
| RestRole value; | |||||
| if (_roles.TryGetValue(id, out value)) | |||||
| return value; | |||||
| return null; | return null; | ||||
| } | } | ||||
| public override async Task DownloadUsersAsync() | |||||
| { | |||||
| await Discord.DownloadUsersAsync(new [] { this }); | |||||
| } | |||||
| public void CompleteDownloadMembers() | |||||
| { | |||||
| _downloaderPromise.TrySetResultAsync(true); | |||||
| } | |||||
| public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | |||||
| public async Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) | |||||
| { | { | ||||
| var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; | |||||
| var voiceState = new VoiceState(voiceChannel, model); | |||||
| (voiceStates ?? _voiceStates)[model.UserId] = voiceState; | |||||
| return voiceState; | |||||
| } | |||||
| public VoiceState? GetVoiceState(ulong id) | |||||
| { | |||||
| VoiceState voiceState; | |||||
| if (_voiceStates.TryGetValue(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| public VoiceState? RemoveVoiceState(ulong id) | |||||
| { | |||||
| VoiceState voiceState; | |||||
| if (_voiceStates.TryRemove(id, out voiceState)) | |||||
| return voiceState; | |||||
| return null; | |||||
| } | |||||
| public async Task<IAudioClient> ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute) | |||||
| { | |||||
| try | |||||
| { | |||||
| TaskCompletionSource<AudioClient> promise; | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await DisconnectAudioInternalAsync().ConfigureAwait(false); | |||||
| promise = new TaskCompletionSource<AudioClient>(); | |||||
| _audioConnectPromise = promise; | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| var timeoutTask = Task.Delay(15000); | |||||
| if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask) | |||||
| throw new TimeoutException(); | |||||
| return await promise.Task.ConfigureAwait(false); | |||||
| } | |||||
| catch (Exception) | |||||
| { | |||||
| await DisconnectAudioInternalAsync().ConfigureAwait(false); | |||||
| throw; | |||||
| } | |||||
| } | |||||
| public async Task DisconnectAudioAsync(AudioClient client = null) | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await DisconnectAudioInternalAsync(client).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| private async Task DisconnectAudioInternalAsync(AudioClient client = null) | |||||
| { | |||||
| var oldClient = AudioClient; | |||||
| if (oldClient != null) | |||||
| { | |||||
| if (client == null || oldClient == client) | |||||
| { | |||||
| _audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection | |||||
| _audioConnectPromise = null; | |||||
| } | |||||
| if (oldClient == client) | |||||
| { | |||||
| AudioClient = null; | |||||
| await oldClient.DisconnectAsync().ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| public async Task FinishConnectAudio(int id, string url, string token) | |||||
| { | |||||
| var voiceState = GetVoiceState(CurrentUser.Id).Value; | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (AudioClient == null) | |||||
| { | |||||
| var audioClient = new AudioClient(this, id); | |||||
| audioClient.Disconnected += async ex => | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client | |||||
| { | |||||
| if (ex != null) | |||||
| { | |||||
| //Reconnect if we still have channel info. | |||||
| //TODO: Is this threadsafe? Could channel data be deleted before we access it? | |||||
| var voiceState2 = GetVoiceState(CurrentUser.Id); | |||||
| if (voiceState2.HasValue) | |||||
| { | |||||
| var voiceChannelId = voiceState2.Value.VoiceChannel?.Id; | |||||
| if (voiceChannelId != null) | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| try { AudioClient.Dispose(); } catch { } | |||||
| AudioClient = null; | |||||
| } | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| }; | |||||
| AudioClient = audioClient; | |||||
| } | |||||
| await AudioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); | |||||
| await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); | |||||
| } | |||||
| catch (OperationCanceledException) | |||||
| { | |||||
| await DisconnectAudioAsync(); | |||||
| } | |||||
| catch (Exception e) | |||||
| { | |||||
| await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false); | |||||
| await DisconnectAudioAsync(); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| public async Task FinishJoinAudioChannel() | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (AudioClient != null) | |||||
| await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| public SocketGuild Clone() => MemberwiseClone() as SocketGuild; | |||||
| new internal ISocketGuildChannel ToChannel(ChannelModel model) | |||||
| { | |||||
| switch (model.Type) | |||||
| { | |||||
| case ChannelType.Text: | |||||
| return new SocketTextChannel(this, model); | |||||
| case ChannelType.Voice: | |||||
| return new SocketVoiceChannel(this, model); | |||||
| default: | |||||
| throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | |||||
| } | |||||
| var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); | |||||
| _roles = _roles.Add(role.Id, role); | |||||
| return role; | |||||
| } | } | ||||
| bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id; | |||||
| GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions; | |||||
| IAudioClient IGuild.AudioClient => AudioClient; | |||||
| //Users | |||||
| public Task<IReadOnlyCollection<RestGuildUser>> GetUsersAsync() | |||||
| => GuildHelper.GetUsersAsync(this, Discord); | |||||
| public Task<RestGuildUser> GetUserAsync(ulong id) | |||||
| => GuildHelper.GetUserAsync(this, Discord, id); | |||||
| public Task<RestGuildUser> GetCurrentUserAsync() | |||||
| => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id); | |||||
| public Task<int> PruneUsersAsync(int days = 30, bool simulate = false) | |||||
| => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); | |||||
| //IGuild | |||||
| bool IGuild.Available => true; | |||||
| IAudioClient IGuild.AudioClient => null; | |||||
| IReadOnlyCollection<IGuildUser> IGuild.CachedUsers => ImmutableArray.Create<IGuildUser>(); | |||||
| IRole IGuild.EveryoneRole => EveryoneRole; | |||||
| IReadOnlyCollection<IRole> IGuild.Roles => Roles; | |||||
| async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync() | |||||
| => await GetBansAsync(); | |||||
| async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync() | |||||
| => await GetChannelsAsync(); | |||||
| async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id) | |||||
| => await GetChannelAsync(id); | |||||
| IGuildChannel IGuild.GetCachedChannel(ulong id) | |||||
| => null; | |||||
| async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name) | |||||
| => await CreateTextChannelAsync(name); | |||||
| async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name) | |||||
| => await CreateVoiceChannelAsync(name); | |||||
| async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync() | |||||
| => await GetIntegrationsAsync(); | |||||
| async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type) | |||||
| => await CreateIntegrationAsync(id, type); | |||||
| async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync() | |||||
| => await GetInvitesAsync(); | |||||
| IRole IGuild.GetRole(ulong id) | |||||
| => GetRole(id); | |||||
| async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync() | |||||
| => await GetUsersAsync(); | |||||
| async Task<IGuildUser> IGuild.GetUserAsync(ulong id) | |||||
| => await GetUserAsync(id); | |||||
| IGuildUser IGuild.GetCachedUser(ulong id) | |||||
| => null; | |||||
| async Task<IGuildUser> IGuild.GetCurrentUserAsync() | |||||
| => await GetCurrentUserAsync(); | |||||
| Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| @@ -7,53 +9,52 @@ using System.Threading.Tasks; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class MessageCache : MessageManager | |||||
| internal class MessageCache | |||||
| { | { | ||||
| private readonly ConcurrentDictionary<ulong, ISocketMessage> _messages; | |||||
| private readonly ConcurrentDictionary<ulong, SocketMessage> _messages; | |||||
| private readonly ConcurrentQueue<ulong> _orderedMessages; | private readonly ConcurrentQueue<ulong> _orderedMessages; | ||||
| private readonly int _size; | private readonly int _size; | ||||
| public override IReadOnlyCollection<ISocketMessage> Messages => _messages.ToReadOnlyCollection(); | |||||
| public IReadOnlyCollection<SocketMessage> Messages => _messages.ToReadOnlyCollection(); | |||||
| public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel) | |||||
| : base(discord, channel) | |||||
| public MessageCache(DiscordSocketClient discord, IChannel channel) | |||||
| { | { | ||||
| _size = discord.MessageCacheSize; | _size = discord.MessageCacheSize; | ||||
| _messages = new ConcurrentDictionary<ulong, ISocketMessage>(1, (int)(_size * 1.05)); | |||||
| _messages = new ConcurrentDictionary<ulong, SocketMessage>(1, (int)(_size * 1.05)); | |||||
| _orderedMessages = new ConcurrentQueue<ulong>(); | _orderedMessages = new ConcurrentQueue<ulong>(); | ||||
| } | } | ||||
| public override void Add(ISocketMessage message) | |||||
| public void Add(SocketMessage message) | |||||
| { | { | ||||
| if (_messages.TryAdd(message.Id, message)) | if (_messages.TryAdd(message.Id, message)) | ||||
| { | { | ||||
| _orderedMessages.Enqueue(message.Id); | _orderedMessages.Enqueue(message.Id); | ||||
| ulong msgId; | ulong msgId; | ||||
| ISocketMessage msg; | |||||
| SocketMessage msg; | |||||
| while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) | while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) | ||||
| _messages.TryRemove(msgId, out msg); | _messages.TryRemove(msgId, out msg); | ||||
| } | } | ||||
| } | } | ||||
| public override ISocketMessage Remove(ulong id) | |||||
| public SocketMessage Remove(ulong id) | |||||
| { | { | ||||
| ISocketMessage msg; | |||||
| SocketMessage msg; | |||||
| _messages.TryRemove(id, out msg); | _messages.TryRemove(id, out msg); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public override ISocketMessage Get(ulong id) | |||||
| public SocketMessage Get(ulong id) | |||||
| { | { | ||||
| ISocketMessage result; | |||||
| SocketMessage result; | |||||
| if (_messages.TryGetValue(id, out result)) | if (_messages.TryGetValue(id, out result)) | ||||
| return result; | return result; | ||||
| return null; | return null; | ||||
| } | } | ||||
| public override IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| public IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||||
| { | { | ||||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | ||||
| if (limit == 0) return ImmutableArray<ISocketMessage>.Empty; | |||||
| if (limit == 0) return ImmutableArray<SocketMessage>.Empty; | |||||
| IEnumerable<ulong> cachedMessageIds; | IEnumerable<ulong> cachedMessageIds; | ||||
| if (fromMessageId == null) | if (fromMessageId == null) | ||||
| @@ -67,7 +68,7 @@ namespace Discord.WebSocket | |||||
| .Take(limit) | .Take(limit) | ||||
| .Select(x => | .Select(x => | ||||
| { | { | ||||
| ISocketMessage msg; | |||||
| SocketMessage msg; | |||||
| if (_messages.TryGetValue(x, out msg)) | if (_messages.TryGetValue(x, out msg)) | ||||
| return msg; | return msg; | ||||
| return null; | return null; | ||||
| @@ -75,13 +76,5 @@ namespace Discord.WebSocket | |||||
| .Where(x => x != null) | .Where(x => x != null) | ||||
| .ToImmutableArray(); | .ToImmutableArray(); | ||||
| } | } | ||||
| public override async Task<ISocketMessage> DownloadAsync(ulong id) | |||||
| { | |||||
| var msg = Get(id); | |||||
| if (msg != null) | |||||
| return msg; | |||||
| return await base.DownloadAsync(id).ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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 | 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; | using Model = Discord.API.Message; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketUserMessage : UserMessage, ISocketMessage | |||||
| internal class SocketUserMessage : SocketMessage, IUserMessage | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | |||||
| private long? _editedTimestampTicks; | |||||
| private ImmutableArray<RestAttachment> _attachments; | |||||
| private ImmutableArray<RestEmbed> _embeds; | |||||
| private ImmutableArray<ulong> _mentionedChannelIds; | |||||
| private ImmutableArray<RestRole> _mentionedRoles; | |||||
| private ImmutableArray<RestUser> _mentionedUsers; | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel; | |||||
| public override bool IsTTS => _isTTS; | |||||
| public override bool IsPinned => _isPinned; | |||||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||||
| public SocketUserMessage(ISocketMessageChannel channel, IUser author, Model model) | |||||
| : base(channel, author, model) | |||||
| public override IReadOnlyCollection<IAttachment> Attachments => _attachments; | |||||
| public override IReadOnlyCollection<IEmbed> Embeds => _embeds; | |||||
| public override IReadOnlyCollection<ulong> MentionedChannelIds => _mentionedChannelIds; | |||||
| public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles; | |||||
| public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers; | |||||
| internal SocketUserMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) | |||||
| : base(discord, id, channelId, author) | |||||
| { | |||||
| } | |||||
| internal new static SocketUserMessage Create(DiscordSocketClient discord, SocketUser author, Model model) | |||||
| { | |||||
| var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal override void Update(Model model) | |||||
| { | { | ||||
| base.Update(model); | |||||
| if (model.IsTextToSpeech.IsSpecified) | |||||
| _isTTS = model.IsTextToSpeech.Value; | |||||
| if (model.Pinned.IsSpecified) | |||||
| _isPinned = model.Pinned.Value; | |||||
| if (model.EditedTimestamp.IsSpecified) | |||||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||||
| if (model.MentionEveryone.IsSpecified) | |||||
| _isMentioningEveryone = model.MentionEveryone.Value; | |||||
| if (model.Attachments.IsSpecified) | |||||
| { | |||||
| var value = model.Attachments.Value; | |||||
| if (value.Length > 0) | |||||
| { | |||||
| var attachments = ImmutableArray.CreateBuilder<RestAttachment>(value.Length); | |||||
| for (int i = 0; i < value.Length; i++) | |||||
| attachments.Add(RestAttachment.Create(value[i])); | |||||
| _attachments = attachments.ToImmutable(); | |||||
| } | |||||
| else | |||||
| _attachments = ImmutableArray.Create<RestAttachment>(); | |||||
| } | |||||
| if (model.Embeds.IsSpecified) | |||||
| { | |||||
| var value = model.Embeds.Value; | |||||
| if (value.Length > 0) | |||||
| { | |||||
| var embeds = ImmutableArray.CreateBuilder<RestEmbed>(value.Length); | |||||
| for (int i = 0; i < value.Length; i++) | |||||
| embeds.Add(RestEmbed.Create(value[i])); | |||||
| _embeds = embeds.ToImmutable(); | |||||
| } | |||||
| else | |||||
| _embeds = ImmutableArray.Create<RestEmbed>(); | |||||
| } | |||||
| ImmutableArray<RestUser> mentions = ImmutableArray.Create<RestUser>(); | |||||
| if (model.Mentions.IsSpecified) | |||||
| { | |||||
| var value = model.Mentions.Value; | |||||
| if (value.Length > 0) | |||||
| { | |||||
| var newMentions = ImmutableArray.CreateBuilder<RestUser>(value.Length); | |||||
| for (int i = 0; i < value.Length; i++) | |||||
| newMentions.Add(RestUser.Create(Discord, value[i])); | |||||
| mentions = newMentions.ToImmutable(); | |||||
| } | |||||
| } | |||||
| if (model.Content.IsSpecified) | |||||
| { | |||||
| var text = model.Content.Value; | |||||
| _mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions); | |||||
| _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null); | |||||
| _mentionedRoles = MentionsHelper.GetRoleMentions<RestRole>(text, null); | |||||
| model.Content = text; | |||||
| } | |||||
| } | } | ||||
| public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage; | |||||
| public Task ModifyAsync(Action<ModifyMessageParams> func) | |||||
| => MessageHelper.ModifyAsync(this, Discord, func); | |||||
| public Task DeleteAsync() | |||||
| => MessageHelper.DeleteAsync(this, Discord); | |||||
| public Task PinAsync() | |||||
| => MessageHelper.PinAsync(this, Discord); | |||||
| public Task UnpinAsync() | |||||
| => MessageHelper.UnpinAsync(this, Discord); | |||||
| public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, | |||||
| RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) | |||||
| => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); | |||||
| public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, | |||||
| RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) | |||||
| => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); | |||||
| public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | |||||
| RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | |||||
| { | |||||
| text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); | |||||
| text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); | |||||
| text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); | |||||
| text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); | |||||
| return text; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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 Discord.Rest; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using Model = Discord.API.User; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| internal class SocketGroupUser : GroupUser, ISocketUser | |||||
| public class SocketGroupUser : SocketUser, IGroupUser | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new SocketGroupChannel Channel => base.Channel as SocketGroupChannel; | |||||
| public new SocketGlobalUser User => base.User as SocketGlobalUser; | |||||
| public Presence Presence => User.Presence; //{ get; private set; } | |||||
| public override Game Game => Presence.Game; | |||||
| public override UserStatus Status => Presence.Status; | |||||
| public VoiceState? VoiceState => Channel.GetVoiceState(Id); | |||||
| public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | |||||
| public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | |||||
| public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; | |||||
| public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | |||||
| public SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser user) | |||||
| : base(channel, user) | |||||
| internal SocketGroupUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| { | { | ||||
| } | } | ||||
| internal new static SocketGroupUser Create(DiscordSocketClient discord, Model model) | |||||
| { | |||||
| var entity = new SocketGroupUser(discord, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| public SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; | |||||
| ISocketUser ISocketUser.Clone() => Clone(); | |||||
| public override string ToString() => $"{Username}#{Discriminator}"; | |||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; | |||||
| //IVoiceState | |||||
| bool IVoiceState.IsDeafened => false; | |||||
| bool IVoiceState.IsMuted => false; | |||||
| bool IVoiceState.IsSelfDeafened => false; | |||||
| bool IVoiceState.IsSelfMuted => false; | |||||
| bool IVoiceState.IsSuppressed => false; | |||||
| IVoiceChannel IVoiceState.VoiceChannel => null; | |||||
| string IVoiceState.VoiceSessionId => null; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,53 +1,74 @@ | |||||
| using Discord.Rest; | |||||
| using Discord.API.Rest; | |||||
| using Discord.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.GuildMember; | using Model = Discord.API.GuildMember; | ||||
| using PresenceModel = Discord.API.Presence; | using PresenceModel = Discord.API.Presence; | ||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketGuildUser : GuildUser, ISocketUser, IVoiceState | |||||
| internal class SocketGuildUser : SocketUser, IGuildUser | |||||
| { | { | ||||
| internal override bool IsAttached => true; | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new SocketGuild Guild => base.Guild as SocketGuild; | |||||
| public new SocketGlobalUser User => base.User as SocketGlobalUser; | |||||
| public Presence Presence => User.Presence; //{ get; private set; } | |||||
| public override Game Game => Presence.Game; | |||||
| public override UserStatus Status => Presence.Status; | |||||
| public VoiceState? VoiceState => Guild.GetVoiceState(Id); | |||||
| public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | |||||
| public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | |||||
| public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; | |||||
| public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | |||||
| public bool IsDeafened => VoiceState?.IsDeafened ?? false; | |||||
| public bool IsMuted => VoiceState?.IsMuted ?? false; | |||||
| public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; | |||||
| public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, Model model) | |||||
| : base(guild, user, model) | |||||
| private long? _joinedAtTicks; | |||||
| private ImmutableArray<ulong> _roleIds; | |||||
| public string Nickname { get; private set; } | |||||
| public ulong GuildId { get; private set; } | |||||
| public IReadOnlyCollection<ulong> RoleIds => _roleIds; | |||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | |||||
| internal SocketGuildUser(DiscordSocketClient discord, ulong id) | |||||
| : base(discord, id) | |||||
| { | { | ||||
| //Presence = new Presence(null, UserStatus.Offline); | |||||
| } | } | ||||
| public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, PresenceModel model) | |||||
| : base(guild, user, model) | |||||
| internal static SocketGuildUser Create(DiscordSocketClient discord, Model model) | |||||
| { | { | ||||
| var entity = new SocketGuildUser(discord, model.User.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | } | ||||
| public override void Update(PresenceModel model) | |||||
| internal void Update(Model model) | |||||
| { | { | ||||
| base.Update(model, source); | |||||
| _joinedAtTicks = model.JoinedAt.UtcTicks; | |||||
| if (model.Nick.IsSpecified) | |||||
| Nickname = model.Nick.Value; | |||||
| UpdateRoles(model.Roles); | |||||
| } | |||||
| private void UpdateRoles(ulong[] roleIds) | |||||
| { | |||||
| var roles = ImmutableArray.CreateBuilder<ulong>(roleIds.Length + 1); | |||||
| roles.Add(GuildId); | |||||
| for (int i = 0; i < roleIds.Length; i++) | |||||
| roles.Add(roleIds[i]); | |||||
| _roleIds = roles.ToImmutable(); | |||||
| } | |||||
| var game = model.Game != null ? new Game(model.Game) : null; | |||||
| //Presence = new Presence(game, model.Status); | |||||
| public override async Task UpdateAsync() | |||||
| => Update(await UserHelper.GetAsync(this, Discord)); | |||||
| public Task ModifyAsync(Action<ModifyGuildMemberParams> func) | |||||
| => UserHelper.ModifyAsync(this, Discord, func); | |||||
| public Task KickAsync() | |||||
| => UserHelper.KickAsync(this, Discord); | |||||
| User.Update(model, source); | |||||
| public ChannelPermissions GetPermissions(IGuildChannel channel) | |||||
| { | |||||
| throw new NotImplementedException(); //TODO: Impl | |||||
| } | } | ||||
| IVoiceChannel IVoiceState.VoiceChannel => VoiceState?.VoiceChannel; | |||||
| //IGuildUser | |||||
| IReadOnlyCollection<ulong> IGuildUser.RoleIds => RoleIds; | |||||
| public SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; | |||||
| ISocketUser ISocketUser.Clone() => Clone(); | |||||
| //IVoiceState | |||||
| bool IVoiceState.IsDeafened => false; | |||||
| bool IVoiceState.IsMuted => false; | |||||
| bool IVoiceState.IsSelfDeafened => false; | |||||
| bool IVoiceState.IsSelfMuted => false; | |||||
| bool IVoiceState.IsSuppressed => false; | |||||
| IVoiceChannel IVoiceState.VoiceChannel => null; | |||||
| string IVoiceState.VoiceSessionId => null; | |||||
| } | } | ||||
| } | } | ||||