@@ -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 | ||||
{ | { | ||||