| @@ -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 | |||||
| @@ -4,6 +4,7 @@ | |||||
| <TargetFramework>netstandard2.1</TargetFramework> | <TargetFramework>netstandard2.1</TargetFramework> | ||||
| <LangVersion>8.0</LangVersion> | <LangVersion>8.0</LangVersion> | ||||
| <Nullable>enable</Nullable> | <Nullable>enable</Nullable> | ||||
| <RootNamespace>Discord</RootNamespace> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| </Project> | </Project> | ||||
| @@ -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; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| using Discord.Socket; | |||||
| using Discord.Socket.Providers; | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| public class DiscordConfig | |||||
| { | |||||
| /// <summary> | |||||
| /// Discord.Net version | |||||
| /// </summary> | |||||
| public const string Version = "3.0.0a0"; | |||||
| /// <summary> | |||||
| /// Discord.Net User-Agent | |||||
| /// </summary> | |||||
| public const string UserAgent = "DiscordBot (https://github.com/discord-net/Discord.Net, " + Version + ")"; | |||||
| /// <summary> | |||||
| /// The default, fallback Gateway URI. This will generally be replaced by <see cref="Rest.IDiscordRestApi.GetGatewayAsync"/>. | |||||
| /// </summary> | |||||
| public static readonly Uri DefaultGatewayUri = new Uri("wss://gateway.discord.gg"); | |||||
| /// <summary> | |||||
| /// The base URL for the Rest API. | |||||
| /// </summary> | |||||
| public string RestApiUrl { get; set; } = "https://discordapp.com/api/v6/"; | |||||
| /// <summary> | |||||
| /// The URI to use when connecting to the gateway. If specified, this will override the URI Discord instructs us to use. | |||||
| /// </summary> | |||||
| public Uri? GatewayUri = null; | |||||
| public SocketFactory SocketFactory { get; set; } = DefaultSocketFactory.Create; | |||||
| } | |||||
| } | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -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<IDiscordRestApi>(config.RestApiUrl, refitSettings); | |||||
| } | |||||
| public Task<GatewayInfo> GetGatewayInfoAsync() | |||||
| { | |||||
| return _api.GetGatewayInfoAsync(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| using System.Threading.Tasks; | |||||
| using Refit; | |||||
| using Discord.Rest.Models; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public interface IDiscordRestApi | |||||
| { | |||||
| [Get("/gateway/bot")] | |||||
| Task<GatewayInfo> GetGatewayInfoAsync(); | |||||
| } | |||||
| } | |||||
| @@ -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<T> DeserializeAsync<T>(HttpContent content) | |||||
| { | |||||
| using var json = await content.ReadAsStreamAsync().ConfigureAwait(false); | |||||
| return await JsonSerializer.DeserializeAsync<T>(json, _serializerOptions).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<HttpContent> SerializeAsync<T>(T data) | |||||
| { | |||||
| var stream = new MemoryStream(); | |||||
| try | |||||
| { | |||||
| await JsonSerializer.SerializeAsync<T>(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; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -2,17 +2,17 @@ using System; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | 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"); | static readonly Uri DefaultGatewayUri = new Uri("wss://gateway.discord.gg"); | ||||
| ISocket Socket { get; set; } | 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) | public async Task ConnectAsync(Uri? gatewayUri) | ||||
| @@ -2,7 +2,7 @@ using System; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Net.Socket | |||||
| namespace Discord.Socket | |||||
| { | { | ||||
| public delegate ISocket SocketFactory(OnAbortionHandler abortionHandler, OnPacketHandler packetHandler); | public delegate ISocket SocketFactory(OnAbortionHandler abortionHandler, OnPacketHandler packetHandler); | ||||
| @@ -3,7 +3,7 @@ using System.Net.WebSockets; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Net.Socket.Providers | |||||
| namespace Discord.Socket.Providers | |||||
| { | { | ||||
| public static class DefaultSocketFactory | public static class DefaultSocketFactory | ||||
| { | { | ||||