diff --git a/TODO b/TODO new file mode 100644 index 000000000..43a38094d --- /dev/null +++ b/TODO @@ -0,0 +1,32 @@ +- REST + - Models + - Preconditions + - Endpoints + - Channel + - Emoji + - Guild + - Invite + - User + - Voice + - Webhook +- Gateway + - Models + - Client + - Socket + * Receive + * Compression + - Voice (long) +- Core + - CDN + - Datastore + - Entities + - Channel + - Emoji + - Guild + - User +- Tests + - Unit test Gateway stability / deadlockability? +- Extensions + - Commands + ? design - use finite's or quahu's + - Interactivity diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 260238f05..806d2361b 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -4,6 +4,7 @@ netstandard2.1 8.0 enable + Discord diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs new file mode 100644 index 000000000..7fdd37045 --- /dev/null +++ b/src/Discord.Net/DiscordClient.cs @@ -0,0 +1,25 @@ +using Discord.Rest; +using Discord.Socket; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Discord +{ + internal class DiscordClient : IDiscordClient + { + public DiscordRestApi Rest => _restApi; + public DiscordGatewayApi Gateway => _gatewayApi; + + private readonly DiscordConfig _config; + private readonly DiscordRestApi _restApi; + private readonly DiscordGatewayApi _gatewayApi; + + public DiscordClient(DiscordConfig config, DiscordRestApi restApi, DiscordGatewayApi gatewayApi) + { + _config = config; + _restApi = restApi; + _gatewayApi = gatewayApi; + } + } +} diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs new file mode 100644 index 000000000..2fb4de090 --- /dev/null +++ b/src/Discord.Net/DiscordConfig.cs @@ -0,0 +1,33 @@ +using Discord.Socket; +using Discord.Socket.Providers; +using System; + +namespace Discord +{ + public class DiscordConfig + { + /// + /// Discord.Net version + /// + public const string Version = "3.0.0a0"; + /// + /// Discord.Net User-Agent + /// + public const string UserAgent = "DiscordBot (https://github.com/discord-net/Discord.Net, " + Version + ")"; + + /// + /// The default, fallback Gateway URI. This will generally be replaced by . + /// + public static readonly Uri DefaultGatewayUri = new Uri("wss://gateway.discord.gg"); + /// + /// The base URL for the Rest API. + /// + public string RestApiUrl { get; set; } = "https://discordapp.com/api/v6/"; + /// + /// The URI to use when connecting to the gateway. If specified, this will override the URI Discord instructs us to use. + /// + public Uri? GatewayUri = null; + + public SocketFactory SocketFactory { get; set; } = DefaultSocketFactory.Create; + } +} diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net/IDiscordClient.cs new file mode 100644 index 000000000..913726676 --- /dev/null +++ b/src/Discord.Net/IDiscordClient.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Discord.Rest; +using Discord.Socket; + +namespace Discord +{ + internal interface IDiscordClient + { + static IDiscordClient Create(DiscordConfig config) + { + var rest = new DiscordRestApi(config); + var gateway = new DiscordGatewayApi(config); + + return new DiscordClient(config, rest, gateway); + } + + DiscordRestApi Rest { get; } + DiscordGatewayApi Gateway { get; } + } +} diff --git a/src/Discord.Net/Rest/DiscordRestApi.cs b/src/Discord.Net/Rest/DiscordRestApi.cs new file mode 100644 index 000000000..052d4adc4 --- /dev/null +++ b/src/Discord.Net/Rest/DiscordRestApi.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using System.Threading.Tasks; +using Refit; +using Discord.Rest.Models; + +// This is essentially a reimplementation of Wumpus.Net.Rest +namespace Discord.Rest +{ + public class DiscordRestApi : IDiscordRestApi + { + private readonly IDiscordRestApi _api; + + public DiscordRestApi(DiscordConfig config) + { + var jsonOptions = new JsonSerializerOptions(); + var refitSettings = new RefitSettings + { + ContentSerializer = new JsonContentSerializer(jsonOptions), + }; + _api = RestService.For(config.RestApiUrl, refitSettings); + } + + public Task GetGatewayInfoAsync() + { + return _api.GetGatewayInfoAsync(); + } + } +} diff --git a/src/Discord.Net/Rest/IDiscordRestApi.cs b/src/Discord.Net/Rest/IDiscordRestApi.cs new file mode 100644 index 000000000..3e58c1ce3 --- /dev/null +++ b/src/Discord.Net/Rest/IDiscordRestApi.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Refit; +using Discord.Rest.Models; + +namespace Discord.Rest +{ + public interface IDiscordRestApi + { + [Get("/gateway/bot")] + Task GetGatewayInfoAsync(); + } +} diff --git a/src/Discord.Net/Rest/JsonContentSerializer.cs b/src/Discord.Net/Rest/JsonContentSerializer.cs new file mode 100644 index 000000000..dbbf0f0f4 --- /dev/null +++ b/src/Discord.Net/Rest/JsonContentSerializer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Refit; + +// https://blog.martincostello.com/refit-and-system-text-json/ + +namespace Discord.Rest +{ + public class JsonContentSerializer : IContentSerializer + { + private static readonly MediaTypeHeaderValue _jsonMediaType = new MediaTypeHeaderValue("application/json") { CharSet = Encoding.UTF8.WebName }; + private readonly JsonSerializerOptions _serializerOptions; + + public JsonContentSerializer(JsonSerializerOptions serializerOptions) + { + _serializerOptions = serializerOptions; + } + + public async Task DeserializeAsync(HttpContent content) + { + using var json = await content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonSerializer.DeserializeAsync(json, _serializerOptions).ConfigureAwait(false); + } + + public async Task SerializeAsync(T data) + { + var stream = new MemoryStream(); + try + { + await JsonSerializer.SerializeAsync(stream, data, _serializerOptions).ConfigureAwait(false); + await stream.FlushAsync(); + + var content = new StreamContent(stream); + content.Headers.ContentType = _jsonMediaType; + + return content; + } + catch + { + await stream.DisposeAsync().ConfigureAwait(false); + throw; + } + } + } +} diff --git a/src/Discord.Net/Rest/Models/GatewayInfo.cs b/src/Discord.Net/Rest/Models/GatewayInfo.cs new file mode 100644 index 000000000..e1497be06 --- /dev/null +++ b/src/Discord.Net/Rest/Models/GatewayInfo.cs @@ -0,0 +1,25 @@ +#pragma warning disable CS8618 // Uninitialized NRT expected in models +using System.Text.Json.Serialization; + +namespace Discord.Rest.Models +{ + public class GatewayInfo + { + [JsonPropertyName("url")] + public string Url { get; set; } + [JsonPropertyName("shards")] + public int Shards { get; set; } + [JsonPropertyName("session_start_limit")] + public GatewaySessionStartInfo SessionStartInfo { get; set; } + } + + public class GatewaySessionStartInfo + { + [JsonPropertyName("total")] + public int Total { get; set; } + [JsonPropertyName("remaining")] + public int Remaining { get; set; } + [JsonPropertyName("reset_after")] + public int ResetAfter { get; set; } + } +} diff --git a/src/Discord.Net/Rest/Requests/Test.cs b/src/Discord.Net/Rest/Requests/Test.cs new file mode 100644 index 000000000..e69de29bb diff --git a/src/Discord.Net/Socket/Gateway.cs b/src/Discord.Net/Socket/DiscordGatewayApi.cs similarity index 77% rename from src/Discord.Net/Socket/Gateway.cs rename to src/Discord.Net/Socket/DiscordGatewayApi.cs index df4e1ab6d..ad2305d29 100644 --- a/src/Discord.Net/Socket/Gateway.cs +++ b/src/Discord.Net/Socket/DiscordGatewayApi.cs @@ -2,17 +2,17 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.Socket +namespace Discord.Socket { - public class Gateway + public class DiscordGatewayApi { static readonly Uri DefaultGatewayUri = new Uri("wss://gateway.discord.gg"); ISocket Socket { get; set; } - public Gateway(SocketFactory socketFactory) + public DiscordGatewayApi(DiscordConfig config) { - Socket = socketFactory(OnAborted, OnPacket); + Socket = config.SocketFactory(OnAborted, OnPacket); } public async Task ConnectAsync(Uri? gatewayUri) diff --git a/src/Discord.Net/Socket/ISocket.cs b/src/Discord.Net/Socket/ISocket.cs index 0c65e0828..98a0eacca 100644 --- a/src/Discord.Net/Socket/ISocket.cs +++ b/src/Discord.Net/Socket/ISocket.cs @@ -2,7 +2,7 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.Socket +namespace Discord.Socket { public delegate ISocket SocketFactory(OnAbortionHandler abortionHandler, OnPacketHandler packetHandler); diff --git a/src/Discord.Net/Socket/Providers/DefaultSocket.cs b/src/Discord.Net/Socket/Providers/DefaultSocket.cs index c4501d2a5..66fab72ce 100644 --- a/src/Discord.Net/Socket/Providers/DefaultSocket.cs +++ b/src/Discord.Net/Socket/Providers/DefaultSocket.cs @@ -3,7 +3,7 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.Socket.Providers +namespace Discord.Socket.Providers { public static class DefaultSocketFactory {