Authentication flow implemented in Gateway
This commit is contained in:
parent
6ec2e1d6cc
commit
615a6b98fa
@ -336,7 +336,6 @@ public class JsonTests
|
|||||||
{
|
{
|
||||||
Token = "token_lol",
|
Token = "token_lol",
|
||||||
Intents = 6969,
|
Intents = 6969,
|
||||||
Shard = [1, 2],
|
|
||||||
LargeThreshold = 42
|
LargeThreshold = 42
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
26
Discord.API/DataTypes/Intents.cs
Normal file
26
Discord.API/DataTypes/Intents.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum Intents : ulong {
|
||||||
|
Guilds = 1,
|
||||||
|
GuildMembers = 2,
|
||||||
|
GuildModeration = 4,
|
||||||
|
GuildEmojisAndStickers = 8,
|
||||||
|
GuildIntegrations = 16,
|
||||||
|
GuildWebhooks = 32,
|
||||||
|
GuildInvites = 64,
|
||||||
|
GuildVoiceStates = 128,
|
||||||
|
GuildPresences = 256,
|
||||||
|
GuildMessages = 512,
|
||||||
|
GuildMessageReactions = 1024,
|
||||||
|
GuildMessageTyping = 2048,
|
||||||
|
DirectMessages = 4096,
|
||||||
|
DirectMessageReactions = 8192,
|
||||||
|
DirectMessageTyping = 16384,
|
||||||
|
MessageContent = 32768,
|
||||||
|
GuildScheduledEvents = 65536,
|
||||||
|
AutoModerationConfiguration = 1048576,
|
||||||
|
AutoModerationExecution = 2097152,
|
||||||
|
GuildMessagePolls = 16777216,
|
||||||
|
DirectMessagePolls = 33554432
|
||||||
|
}
|
||||||
@ -8,12 +8,14 @@ public class DiscordClient
|
|||||||
private static readonly string ApiBaseUrl = "https://discord.com/api/v10"; //TODO Export version number to a separate constant
|
private static readonly string ApiBaseUrl = "https://discord.com/api/v10"; //TODO Export version number to a separate constant
|
||||||
|
|
||||||
private string ApiKey;
|
private string ApiKey;
|
||||||
|
private readonly Intents Intents;
|
||||||
private RestClient Rest;
|
private RestClient Rest;
|
||||||
private GatewayClient? Gateway;
|
private GatewayClient? Gateway;
|
||||||
private TimeProvider TimeProvider;
|
private TimeProvider TimeProvider;
|
||||||
|
|
||||||
public DiscordClient(string api_key){
|
public DiscordClient(string api_key, Intents intents){
|
||||||
TimeProvider = TimeProvider.System;
|
TimeProvider = TimeProvider.System;
|
||||||
|
this.Intents=intents;
|
||||||
this.ApiKey = api_key;
|
this.ApiKey = api_key;
|
||||||
Rest = new RestClient(ApiBaseUrl, ApiKey);
|
Rest = new RestClient(ApiBaseUrl, ApiKey);
|
||||||
Task.Run(ConnectGateway);
|
Task.Run(ConnectGateway);
|
||||||
@ -43,8 +45,13 @@ public class DiscordClient
|
|||||||
|
|
||||||
Log.Information("DiscordClient: Gateway url: {gateway_url}", resp.Value.Url);
|
Log.Information("DiscordClient: Gateway url: {gateway_url}", resp.Value.Url);
|
||||||
|
|
||||||
Gateway = new GatewayClient(resp.Value.Url, ApiKey, TimeProvider);
|
Gateway = new GatewayClient(resp.Value.Url, ApiKey, TimeProvider, (ulong)Intents);
|
||||||
|
|
||||||
Log.Information("DiscordClient: Started gateway");
|
Log.Information("DiscordClient: Started gateway");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task Close(){
|
||||||
|
if(Gateway is not null)
|
||||||
|
await Gateway.Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,4 +96,9 @@ public abstract class AbstractGateway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public async Task Close(){
|
||||||
|
StopHeartbeat();
|
||||||
|
await WebsocketClient.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Shutdown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -9,16 +9,21 @@ public class GatewayClient : AbstractGateway {
|
|||||||
|
|
||||||
private const int ApiVersion = 10;
|
private const int ApiVersion = 10;
|
||||||
|
|
||||||
private string ApiKey;
|
private readonly string ApiKey;
|
||||||
|
private readonly ulong Intents;
|
||||||
private ulong? Sequence = null;
|
private ulong? Sequence = null;
|
||||||
|
private string? SessionId = null;
|
||||||
|
private Uri StartingUri;
|
||||||
|
|
||||||
public GatewayClient(string url, string api_key, TimeProvider time_provider)
|
public GatewayClient(string url, string api_key, TimeProvider time_provider, ulong intents)
|
||||||
: this(url, api_key, time_provider, uri => new WebsocketClient(uri))
|
: this(url, api_key, time_provider, uri => new WebsocketClient(uri), intents)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
internal GatewayClient(string url, string api_key, TimeProvider time_provider, Func<Uri, IWebsocketClient> websocket_client_factory)
|
internal GatewayClient(string url, string api_key, TimeProvider time_provider, Func<Uri, IWebsocketClient> websocket_client_factory, ulong intents)
|
||||||
: base(websocket_client_factory.Invoke(BuildUrl(url)), time_provider){
|
: base(websocket_client_factory.Invoke(BuildUrl(url)), time_provider){
|
||||||
this.ApiKey=api_key;
|
this.ApiKey=api_key;
|
||||||
|
this.Intents = intents;
|
||||||
|
this.StartingUri = WebsocketClient.Url;
|
||||||
Log.Debug("GATEWAY: Created new gateway, with url: {url}", WebsocketClient.Url);
|
Log.Debug("GATEWAY: Created new gateway, with url: {url}", WebsocketClient.Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +38,16 @@ public class GatewayClient : AbstractGateway {
|
|||||||
|
|
||||||
protected override void DisconnectHandler(DisconnectionInfo info){
|
protected override void DisconnectHandler(DisconnectionInfo info){
|
||||||
Log.Information("GATEWAY: Disconnected. Type: {DisconnectionType}", info.Type);
|
Log.Information("GATEWAY: Disconnected. Type: {DisconnectionType}", info.Type);
|
||||||
|
Log.Debug("GATEWAY: Connection closed due to {code} {description}", info.CloseStatus, info.CloseStatusDescription);
|
||||||
|
|
||||||
|
if((int?)info.CloseStatus is 4004 or 4008 or (>= 4010 and <= 4014)){
|
||||||
|
Log.Error("GATEWAY: Disconnection due to error: close code: {code}, desc: {desc}", info.CloseStatus, info.CloseStatusDescription);
|
||||||
|
info.CancelReconnection = true;
|
||||||
|
}else if((int?)info.CloseStatus is 4007 or 4009){
|
||||||
|
SessionId = null;
|
||||||
|
Sequence = null;
|
||||||
|
WebsocketClient.Url = StartingUri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
protected override void ReconnectHandler(ReconnectionInfo info){
|
protected override void ReconnectHandler(ReconnectionInfo info){
|
||||||
Log.Information("GATEWAY: (Re)Connected to server. Url: {url}, Type: {Type}", WebsocketClient.Url, info.Type);
|
Log.Information("GATEWAY: (Re)Connected to server. Url: {url}, Type: {Type}", WebsocketClient.Url, info.Type);
|
||||||
@ -43,7 +58,7 @@ public class GatewayClient : AbstractGateway {
|
|||||||
GatewayPacket packet = JsonSerializer.Deserialize(msg.Text!, SourceGenerationContext.Default.GatewayPacket)
|
GatewayPacket packet = JsonSerializer.Deserialize(msg.Text!, SourceGenerationContext.Default.GatewayPacket)
|
||||||
?? throw new Exception("Failed to deserialize packet"); // This can be optimized //TODO
|
?? throw new Exception("Failed to deserialize packet"); // This can be optimized //TODO
|
||||||
|
|
||||||
Log.Debug("GATEWAY: Packet received");
|
Log.Debug("GATEWAY: Packet received, opcode: {opcode}", packet.Op);
|
||||||
|
|
||||||
switch(packet){
|
switch(packet){
|
||||||
case HelloPacket helloPacket:
|
case HelloPacket helloPacket:
|
||||||
@ -52,8 +67,14 @@ public class GatewayClient : AbstractGateway {
|
|||||||
case HeartbeatAckPacket:
|
case HeartbeatAckPacket:
|
||||||
HeartbeatAckHandler();
|
HeartbeatAckHandler();
|
||||||
break;
|
break;
|
||||||
|
case DispatchPacket dispatchPacket:
|
||||||
|
DispatchHandler(dispatchPacket);
|
||||||
|
break;
|
||||||
|
case InvalidSessionPacket invalidSessionPacket:
|
||||||
|
InvalidSessionHandler(invalidSessionPacket);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log.Debug("GATEWAY: Packet not handled");
|
Log.Debug("GATEWAY: Packet not handled {opcode}", packet.Op);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}catch(Exception ex){
|
}catch(Exception ex){
|
||||||
@ -64,6 +85,7 @@ public class GatewayClient : AbstractGateway {
|
|||||||
private void HelloPacketHandler(HelloPacket packet){
|
private void HelloPacketHandler(HelloPacket packet){
|
||||||
StartHeartbeat((int)packet.Data.HeartbeatInterval);
|
StartHeartbeat((int)packet.Data.HeartbeatInterval);
|
||||||
Log.Debug("GATEWAY: Hello packet received, heartbeat interval: {heartbeat_ms}", packet.Data.HeartbeatInterval);
|
Log.Debug("GATEWAY: Hello packet received, heartbeat interval: {heartbeat_ms}", packet.Data.HeartbeatInterval);
|
||||||
|
Login();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HeartbeatAckHandler(){
|
private void HeartbeatAckHandler(){
|
||||||
@ -71,6 +93,32 @@ public class GatewayClient : AbstractGateway {
|
|||||||
Log.Information("GATEWAY: Heartbeat ping: {ping_ms} ms", HeartbeatAckReceived());
|
Log.Information("GATEWAY: Heartbeat ping: {ping_ms} ms", HeartbeatAckReceived());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DispatchHandler(DispatchPacket packet){
|
||||||
|
Log.Debug("GATEWAY: Event: {event}, seq: {sequence}", packet.Event, packet.Sequence);
|
||||||
|
Sequence = packet.Sequence;
|
||||||
|
switch(packet){
|
||||||
|
case ReadyPacket ready_packet:
|
||||||
|
ReadyHandler(ready_packet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvalidSessionHandler(InvalidSessionPacket packet){
|
||||||
|
Log.Debug("GATEWAY: Invalid session resumable: {resumable}", packet.Resume);
|
||||||
|
if(packet.Resume != true){ // This look awful tbh
|
||||||
|
SessionId=null;
|
||||||
|
Sequence=null;
|
||||||
|
WebsocketClient.Url=StartingUri;
|
||||||
|
_ = WebsocketClient.Reconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadyHandler(ReadyPacket packet){
|
||||||
|
SessionId = packet.Data.SessionId;
|
||||||
|
WebsocketClient.Url = BuildUrl(packet.Data.ResumeGatewayUrl);
|
||||||
|
Log.Debug("GATEWAY: Resume url: {url}", packet.Data.ResumeGatewayUrl);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Task SendHeartbeat()
|
protected override Task SendHeartbeat()
|
||||||
{
|
{
|
||||||
HeartbeatPacket packet = new(){
|
HeartbeatPacket packet = new(){
|
||||||
@ -87,4 +135,29 @@ public class GatewayClient : AbstractGateway {
|
|||||||
Log.Debug("GATEWAY: Heartbeat ack missed. Reconnecting");
|
Log.Debug("GATEWAY: Heartbeat ack missed. Reconnecting");
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Login(){
|
||||||
|
if(SessionId != null && Sequence != null){
|
||||||
|
// Resume
|
||||||
|
ResumePacket packet = new(){
|
||||||
|
Data=new(){
|
||||||
|
Token=ApiKey,
|
||||||
|
Sequence=Sequence.Value,
|
||||||
|
SessionId=SessionId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
WebsocketClient.Send(JsonSerializer.Serialize(packet, SourceGenerationContext.Default.ResumePacket));
|
||||||
|
Log.Debug("GATEWAY: Resuming previus session");
|
||||||
|
}else{
|
||||||
|
// Identify
|
||||||
|
IdentifyPacket packet = new(){
|
||||||
|
Data=new(){
|
||||||
|
Intents = Intents,
|
||||||
|
Token = ApiKey
|
||||||
|
}
|
||||||
|
};
|
||||||
|
WebsocketClient.Send(JsonSerializer.Serialize(packet, SourceGenerationContext.Default.IdentifyPacket));
|
||||||
|
Log.Debug("GATEWAY: Identifying for a new session");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace Discord.API;
|
|||||||
|
|
||||||
internal class ChannelCreatePacket : DispatchPacket
|
internal class ChannelCreatePacket : DispatchPacket
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public override string Event => "CHANNEL_CREATE";
|
public override string Event => "CHANNEL_CREATE";
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace Discord.API;
|
|||||||
|
|
||||||
internal class ChannelDeletePacket : DispatchPacket
|
internal class ChannelDeletePacket : DispatchPacket
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public override string Event => "CHANNEL_DELETE";
|
public override string Event => "CHANNEL_DELETE";
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace Discord.API;
|
|||||||
|
|
||||||
internal class ChannelUpdatePacket : DispatchPacket
|
internal class ChannelUpdatePacket : DispatchPacket
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public override string Event => "CHANNEL_UPDATE";
|
public override string Event => "CHANNEL_UPDATE";
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
internal class GenericDispatchPacket : DispatchPacket
|
||||||
|
{
|
||||||
|
private string _event;
|
||||||
|
[JsonIgnore]
|
||||||
|
public override string Event => _event;
|
||||||
|
public GenericDispatchPacket(string t, ulong? s){
|
||||||
|
_event=t;
|
||||||
|
Sequence = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ namespace Discord.API;
|
|||||||
|
|
||||||
internal class GuildCreatePacket : DispatchPacket
|
internal class GuildCreatePacket : DispatchPacket
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public override string Event => "GUILD_CREATE";
|
public override string Event => "GUILD_CREATE";
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
[JsonPropertyName("d")]
|
[JsonPropertyName("d")]
|
||||||
|
|||||||
@ -4,6 +4,7 @@ namespace Discord.API;
|
|||||||
|
|
||||||
internal class GuildUpdatePacket : DispatchPacket
|
internal class GuildUpdatePacket : DispatchPacket
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public override string Event => "GUILD_UPDATE";
|
public override string Event => "GUILD_UPDATE";
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
[JsonPropertyName("d")]
|
[JsonPropertyName("d")]
|
||||||
|
|||||||
@ -2,10 +2,9 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace Discord.API;
|
namespace Discord.API;
|
||||||
|
|
||||||
[JsonPolymorphic(TypeDiscriminatorPropertyName = "t", IgnoreUnrecognizedTypeDiscriminators = true, UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
|
|
||||||
[JsonDerivedType(typeof(ReadyPacket))]
|
|
||||||
internal class ReadyPacket : DispatchPacket
|
internal class ReadyPacket : DispatchPacket
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public override string Event => "READY";
|
public override string Event => "READY";
|
||||||
public struct ReadyData
|
public struct ReadyData
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
|
using System.Reflection.Metadata.Ecma335;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
@ -57,7 +58,8 @@ internal class GatewayPacketConverter : JsonConverter<GatewayPacket>
|
|||||||
"GUILD_UPDATE" => json_doc.Deserialize(
|
"GUILD_UPDATE" => json_doc.Deserialize(
|
||||||
SourceGenerationContext.Default.GuildUpdatePacket
|
SourceGenerationContext.Default.GuildUpdatePacket
|
||||||
),
|
),
|
||||||
_ => throw new NotSupportedException($"Packet {event_name} is not supported in json deserialization")
|
not null => new GenericDispatchPacket(event_name, json_doc.RootElement.GetProperty("s").GetUInt64()),
|
||||||
|
_ => throw new JsonException("Event name was null")
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Opcode {op} is not supported in json deserialization");
|
throw new NotSupportedException($"Opcode {op} is not supported in json deserialization");
|
||||||
@ -78,7 +80,8 @@ internal class GatewayPacketConverter : JsonConverter<GatewayPacket>
|
|||||||
writer.WriteRawValue(JsonSerializer.SerializeToUtf8Bytes(value, SourceGenerationContext.Default.ResumePacket));
|
writer.WriteRawValue(JsonSerializer.SerializeToUtf8Bytes(value, SourceGenerationContext.Default.ResumePacket));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
writer.WriteRawValue(JsonSerializer.SerializeToUtf8Bytes(value, SourceGenerationContext.Default.GatewayPacket));
|
//writer.WriteRawValue(JsonSerializer.SerializeToUtf8Bytes(value, SourceGenerationContext.Default.GatewayPacket));
|
||||||
|
//this is bad //TODO
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ internal class IdentifyPacket : GatewayPacket
|
|||||||
public PropertiesClass Properties => PropertiesClass.Instance;
|
public PropertiesClass Properties => PropertiesClass.Instance;
|
||||||
[JsonInclude]
|
[JsonInclude]
|
||||||
public bool Compress => false;
|
public bool Compress => false;
|
||||||
public int LargeThreshold = 250;
|
public int? LargeThreshold = 250;
|
||||||
public int[]? Shard = null;
|
public int[]? Shard = null;
|
||||||
//presence
|
//presence
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
|||||||
@ -6,5 +6,5 @@ internal class InvalidSessionPacket : GatewayPacket
|
|||||||
{
|
{
|
||||||
public override Opcode Op => Opcode.InvalidSession;
|
public override Opcode Op => Opcode.InvalidSession;
|
||||||
[JsonPropertyName("d")]
|
[JsonPropertyName("d")]
|
||||||
public bool? Reconnect { get; init; }
|
public bool? Resume { get; init; }
|
||||||
}
|
}
|
||||||
@ -5,6 +5,7 @@ namespace Discord.API;
|
|||||||
[JsonSourceGenerationOptions(IgnoreReadOnlyFields = false,
|
[JsonSourceGenerationOptions(IgnoreReadOnlyFields = false,
|
||||||
IgnoreReadOnlyProperties = false,
|
IgnoreReadOnlyProperties = false,
|
||||||
IncludeFields = true,
|
IncludeFields = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
|
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
|
||||||
Converters = [
|
Converters = [
|
||||||
typeof(GatewayPacketConverter),
|
typeof(GatewayPacketConverter),
|
||||||
|
|||||||
@ -8,6 +8,8 @@ Log.Logger=new LoggerConfiguration()
|
|||||||
|
|
||||||
string api_key = File.ReadAllText("api_key.txt");
|
string api_key = File.ReadAllText("api_key.txt");
|
||||||
|
|
||||||
DiscordClient client = new DiscordClient(api_key);
|
DiscordClient client = new DiscordClient(api_key, Intents.Guilds);
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|
||||||
|
await client.Close();
|
||||||
@ -14,6 +14,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<PublishAot>true</PublishAot>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user