From a39f27f1cc595a76b87ac435ade5cb87249cfd39 Mon Sep 17 00:00:00 2001
From: Emilia <emilia@jumpsca.re>
Date: Sat, 25 May 2024 17:06:24 +0300
Subject: [PATCH] something along the lines of v4 support

---
 SharpQuark/ApiResult/UserApiResult.cs |  2 +-
 SharpQuark/Lightquark.cs              | 13 ++--
 SharpQuark/Methods/Gateway.cs         |  3 +
 SharpQuark/NetworkInformation.cs      |  4 +-
 SharpQuark/Objects/Channel.cs         | 10 +--
 SharpQuark/Objects/GatewayMessage.cs  | 19 +++++-
 SharpQuark/Objects/Id/QuarkId.cs      |  2 +-
 SharpQuark/Objects/Message.cs         | 17 ++++-
 SharpQuark/Objects/Quark.cs           | 92 +++++++++++++++++++++++++++
 SharpQuark/Token/Token.cs             |  1 +
 SharpQuark/Token/TokenCredential.cs   |  4 +-
 11 files changed, 146 insertions(+), 21 deletions(-)
 create mode 100644 SharpQuark/Objects/Quark.cs

diff --git a/SharpQuark/ApiResult/UserApiResult.cs b/SharpQuark/ApiResult/UserApiResult.cs
index ca9e70d..49d967b 100644
--- a/SharpQuark/ApiResult/UserApiResult.cs
+++ b/SharpQuark/ApiResult/UserApiResult.cs
@@ -17,7 +17,7 @@ public class UserApiResult : BaseApiResult
 
 public class UserMeResponse : Response
 {
-    [JsonProperty("jwtData")]
+    [JsonProperty("user")]
     public required User User;
 }
 
diff --git a/SharpQuark/Lightquark.cs b/SharpQuark/Lightquark.cs
index e4209d7..1d57f0d 100644
--- a/SharpQuark/Lightquark.cs
+++ b/SharpQuark/Lightquark.cs
@@ -16,7 +16,7 @@ public partial class Lightquark : IDisposable
     private static int _sid;
     private readonly int _id;
     private readonly HttpClient _http = new();
-    private static readonly string[] UnsupportedVersions = ["v1", "v2"];
+    private static readonly string[] UnsupportedVersions = ["v1", "v2", "v3"];
     private readonly string _version;
     private readonly TokenCredential _tokenCredential;
     private readonly string _agent;
@@ -25,7 +25,7 @@ public partial class Lightquark : IDisposable
     private Uri BaseUri => new (Network.BaseUrl ?? throw new Exception("Invalid network"));
     private Timer? _heartbeatTimer;
     
-    public Lightquark(TokenCredential credential, NetworkInformation networkInformation, string? agent = null, string version = "v3", bool suppressStartupMessage = false, bool connectWebsocket = true)
+    public Lightquark(TokenCredential credential, NetworkInformation networkInformation, string? agent = null, string version = "v4", bool suppressStartupMessage = false, bool connectWebsocket = true)
     {
         _id = _sid;
         _sid++;
@@ -50,7 +50,6 @@ public partial class Lightquark : IDisposable
         var clientFactory = new Func<ClientWebSocket>(() =>
         {
             var client = new ClientWebSocket();
-            client.Options.AddSubProtocol(_tokenCredential.AccessToken.ToString()!);
             return client;
         });
         _client = new WebsocketClient(new Uri(Network.Gateway), clientFactory);
@@ -67,9 +66,11 @@ public partial class Lightquark : IDisposable
         });
         
         // Start
-        _client.MessageReceived.Subscribe(msg => GatewayMessage(msg));
+        _client.MessageReceived.Subscribe(GatewayMessage);
         _client.StartOrFail();
         
+        SendGateway(new AuthenticateGatewayMessage("authenticate", _tokenCredential.AccessToken));
+        
         _heartbeatTimer = new Timer(
             _ =>
             {
@@ -77,7 +78,7 @@ public partial class Lightquark : IDisposable
                 Debug.WriteLine($"[{_id}] Sent heartbeat message.");
             },
             null,
-            TimeSpan.Zero,
+            TimeSpan.FromSeconds(1),
             TimeSpan.FromSeconds(15));
     }
 
@@ -93,7 +94,7 @@ public partial class Lightquark : IDisposable
         }
         // Prevent falling over for predictable reasons
         if (UnsupportedVersions.Any(v => v == (options?.Version ?? _version)))
-            throw new UnsupportedVersionException("SharpQuark does not support API versions 1 and 2 due to authentication differences");
+            throw new UnsupportedVersionException("SharpQuark only supports version 4.");
 
         // Allow using both "/user/me" and "user/me"
         if (endpoint.StartsWith('/'))
diff --git a/SharpQuark/Methods/Gateway.cs b/SharpQuark/Methods/Gateway.cs
index 1f6c704..e0b7fbb 100644
--- a/SharpQuark/Methods/Gateway.cs
+++ b/SharpQuark/Methods/Gateway.cs
@@ -20,6 +20,9 @@ public partial class Lightquark
                 case "heartbeat":
                     Debug.WriteLine("Heartbeat received");
                     return;
+                case "authenticate":
+                    Debug.WriteLine("Authenticated");
+                    return;
                 default:
                     Console.WriteLine($"Unimplemented event received {baseMessage.Event}");
                     break;
diff --git a/SharpQuark/NetworkInformation.cs b/SharpQuark/NetworkInformation.cs
index 2eea2de..78b3975 100644
--- a/SharpQuark/NetworkInformation.cs
+++ b/SharpQuark/NetworkInformation.cs
@@ -31,10 +31,10 @@ public class NetworkInformation
     [JsonProperty("capabilities")]
     public object? Capabilities;
 
-    public static async Task<NetworkInformation> GetNetwork(string baseUrl)
+    public static async Task<NetworkInformation> GetNetwork(string baseUrl, string version = "v4")
     {
         var baseUri = new Uri(baseUrl);
-        var res = await Http.GetAsync(new Uri(baseUri, "/v3/network"));
+        var res = await Http.GetAsync(new Uri(baseUri, $"/{version}/network"));
         var rawApiResult = await res.Content.ReadAsStringAsync();
         var parsedApiResult = JsonConvert.DeserializeObject<NetworkInformation>(rawApiResult);
         return parsedApiResult ?? throw new InvalidOperationException();
diff --git a/SharpQuark/Objects/Channel.cs b/SharpQuark/Objects/Channel.cs
index a030ffb..64f94c9 100644
--- a/SharpQuark/Objects/Channel.cs
+++ b/SharpQuark/Objects/Channel.cs
@@ -29,11 +29,11 @@ public class ChannelConverter : JsonConverter
 
         // Deserialize other properties
         var name = jsonObject.GetValue("name")?.ToObject<string>();
-        var quarkId = jsonObject.GetValue("quark")?.ToObject<QuarkId>();
+        var quark = jsonObject.GetValue("quark")?.ToObject<Quark>();
         var description = jsonObject.GetValue("description")?.ToObject<string>();
 
         // Create new Channel instance
-        channel = new Channel(channelId, name ?? throw new Exception("Channel Name null"), quarkId ?? throw new Exception("Channel Quark ID null"), description);
+        channel = new Channel(channelId, name ?? throw new Exception("Channel Name null"), quark ?? throw new Exception("Channel Quark ID null"), description);
         Instances[channelId] = channel;
         return channel;
     }
@@ -49,7 +49,7 @@ public class Channel
     [JsonProperty("description")]
     public string Description;
     [JsonProperty("quark")]
-    public QuarkId QuarkId;
+    public Quark Quark;
     [JsonProperty("messages")] 
     public SortedSet<Message> Messages;
 
@@ -57,11 +57,11 @@ public class Channel
 
     [JsonIgnore] private bool _initialLoad;
 
-    internal Channel(ChannelId id, string name, QuarkId quarkId, string? description)
+    internal Channel(ChannelId id, string name, Quark quark, string? description)
     {
         Id = id;
         Name = name;
-        QuarkId = quarkId;
+        Quark = quark;
         Description = description ?? string.Empty;
         Messages = new SortedSet<Message>(new TimestampComparer());
     }
diff --git a/SharpQuark/Objects/GatewayMessage.cs b/SharpQuark/Objects/GatewayMessage.cs
index 6d2bc11..a17f20e 100644
--- a/SharpQuark/Objects/GatewayMessage.cs
+++ b/SharpQuark/Objects/GatewayMessage.cs
@@ -1,16 +1,29 @@
 using Newtonsoft.Json;
+using SharpQuark.Token;
 
 namespace SharpQuark.Objects;
 
-public struct GatewayMessage(string @event, string message, string? state = null)
+public class GatewayMessage(string @event, string? message, string? state = null)
 {
     [JsonProperty("event")] public string Event = @event;
-    [JsonProperty("message")] public string Message = message;
+    [JsonProperty("message")] public string? Message = message;
     [JsonProperty("state")] public string? State = state;
 }
 
+// public class AuthenticateGatewayMessage(string @event, AccessToken token, string? state = null)
+// {
+//     [JsonProperty("eventId")] public required string Event = @event;
+//     [JsonProperty("token")] public string Token = token.ToString()!;
+//     [JsonProperty("state")] public string? State = state;
+// }
+
+public class AuthenticateGatewayMessage(string @event, AccessToken token, string? state = null) : GatewayMessage(@event, null, state)
+{
+    [JsonProperty("token")] public string Token = token.ToString()!;
+}
+
 public class GatewayEventBase
 {
-    [JsonProperty("eventId")] public required string Event;
+    [JsonProperty("event")] public required string Event;
     [JsonProperty("state")] public string? State;
 }
\ No newline at end of file
diff --git a/SharpQuark/Objects/Id/QuarkId.cs b/SharpQuark/Objects/Id/QuarkId.cs
index ef7c4fe..e83b1fd 100644
--- a/SharpQuark/Objects/Id/QuarkId.cs
+++ b/SharpQuark/Objects/Id/QuarkId.cs
@@ -33,7 +33,7 @@ public class QuarkIdConverter : JsonConverter
         {
             return new QuarkId((string?)reader.Value ?? string.Empty);
         }
-        throw new JsonSerializationException("Unexpected token type.");
+        throw new JsonSerializationException($"Unexpected token type. ({reader.TokenType})");
     }
 
     public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
diff --git a/SharpQuark/Objects/Message.cs b/SharpQuark/Objects/Message.cs
index 78ef6ce..88772ae 100644
--- a/SharpQuark/Objects/Message.cs
+++ b/SharpQuark/Objects/Message.cs
@@ -27,7 +27,7 @@ public class Message
     public required bool Edited;
 
     [JsonProperty("attachments")]
-    public required string[] AttachmentLinks; // TODO: Attachment objects with metadata
+    public required Attachment[] Attachment;
 
     [JsonProperty("specialAttributes")]
     public object[] SpecialAttributes = [];
@@ -39,6 +39,21 @@ public class Message
 
 }
 
+public class Attachment
+{
+    [JsonProperty("url")] 
+    public required Uri Uri;
+
+    [JsonProperty("size")] 
+    public long Size;
+
+    [JsonProperty("type")] 
+    public required string ContentType;
+
+    [JsonProperty("filename")] 
+    public required string FileName;
+}
+
 
 public class TimestampComparer : IComparer<Message>
 {
diff --git a/SharpQuark/Objects/Quark.cs b/SharpQuark/Objects/Quark.cs
new file mode 100644
index 0000000..f5ebc00
--- /dev/null
+++ b/SharpQuark/Objects/Quark.cs
@@ -0,0 +1,92 @@
+using System.Diagnostics;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using SharpQuark.Objects.Id;
+
+namespace SharpQuark.Objects;
+
+public class QuarkConverter : JsonConverter
+{
+    private static readonly Dictionary<QuarkId, Quark> Instances = new();
+
+    public override bool CanConvert(Type objectType)
+    {
+        return objectType == typeof(Quark);
+    }
+
+    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+    {
+        throw new NotImplementedException();
+    }
+
+    public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+    {
+        var jsonObject = JObject.Load(reader);
+        var quarkId = jsonObject.GetValue("_id")?.ToObject<QuarkId>();
+                
+        if (Instances.TryGetValue(quarkId ?? throw new Exception("Quark ID null"), out var quark))
+            return quark;
+
+        // Deserialize other properties
+        var name = jsonObject.GetValue("name")?.ToObject<string>();
+        var iconUri = jsonObject.GetValue("iconUri")?.ToObject<Uri>();
+        var invite = jsonObject.GetValue("invite")?.ToObject<string>();
+        var owners = jsonObject.GetValue("owners")?.ToObject<UserId[]>();
+
+        // Create new Channel instance
+        quark = new Quark(quarkId, 
+            name ?? throw new Exception("Quark Name null"), 
+            iconUri ?? throw new Exception("Quark Icon null"),
+            invite ?? throw new Exception("Quark Invite null"), 
+            owners ?? throw new Exception("Quark Owners null"));
+        Instances[quarkId] = quark;
+        return quark;
+    }
+}
+
+[JsonConverter(typeof(QuarkConverter))]
+public class Quark
+{
+    [JsonProperty("_id")]
+    public QuarkId Id;
+    [JsonProperty("name")]
+    public string Name;
+    [JsonProperty("iconUri")]
+    public Uri Icon;
+    [JsonProperty("invite")] 
+    public string Invite;
+    [JsonProperty("owners")] 
+    public UserId[] Owners;
+    // [JsonProperty("channels")] 
+    // public Channel[] Channels;
+
+    [JsonIgnore] public Lightquark Lq = null!;
+
+    [JsonIgnore] private bool _initialLoad;
+
+    internal Quark(QuarkId id, string name, Uri iconUri, string invite, UserId[] owners)
+    {
+        Id = id;
+        Name = name;
+        Icon = iconUri;
+        Invite = invite;
+        Owners = owners;
+        // Channels = [];
+    }
+        
+    // private async Task<Channel[]> GetChannels()
+    // {
+    //     return (await Lq.QuarkChannels(this)).Response.Messages;
+    // }
+
+    public async Task InitialLoad()
+    {
+        if (_initialLoad)
+        {
+            Debug.WriteLine($"[q{Id}] Initial loaded before, skipping");
+            return;
+        }
+        // TODO: get channels
+        _initialLoad = true;
+    }
+};
\ No newline at end of file
diff --git a/SharpQuark/Token/Token.cs b/SharpQuark/Token/Token.cs
index 0254244..09b183d 100644
--- a/SharpQuark/Token/Token.cs
+++ b/SharpQuark/Token/Token.cs
@@ -39,6 +39,7 @@ public class Token
     public static Token From(string token)
     {
         var tokenParts = token.Split(".");
+        Console.WriteLine(token);
         if (tokenParts.Length != 5)
             throw new TokenException($"Wrong token part count: expected 5, got {tokenParts.Length}");
         var prefixPart = tokenParts[0];
diff --git a/SharpQuark/Token/TokenCredential.cs b/SharpQuark/Token/TokenCredential.cs
index e7bd788..240c4d8 100644
--- a/SharpQuark/Token/TokenCredential.cs
+++ b/SharpQuark/Token/TokenCredential.cs
@@ -27,7 +27,7 @@ public class TokenCredential(AccessToken accessToken, RefreshToken refreshToken)
     {
         // Create temporary Lightquark instance
         AuthTokenApiResult res;
-        using (var tempLq = new Lightquark(new TokenCredential(new AccessToken(), new RefreshToken()), networkInformation, null, "v3", true, false))
+        using (var tempLq = new Lightquark(new TokenCredential(new AccessToken(), new RefreshToken()), networkInformation, null, "v4", true, false))
         {
             res = await tempLq.AuthToken(email, password);
         }
@@ -42,7 +42,7 @@ public class TokenCredential(AccessToken accessToken, RefreshToken refreshToken)
     {
         // Create temporary Lightquark instance
         AuthTokenApiResult res;
-        using (var tempLq = new Lightquark(new TokenCredential(new AccessToken(), new RefreshToken()), networkInformation, null, "v3", true))
+        using (var tempLq = new Lightquark(new TokenCredential(new AccessToken(), new RefreshToken()), networkInformation, null, "v4", true))
         {
             res = await tempLq.AuthRegister(email, password, username);
         }
-- 
GitLab