diff --git a/.gitignore b/.gitignore index 40daee9..ffa8fec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +welcome.txt +mariadb.txt +login_key.txt +http-motd.txt +_webdb.txt + # User-specific files *.suo *.user diff --git a/SharpChat.sln b/SharpChat.sln index 0140e92..3230db2 100644 --- a/SharpChat.sln +++ b/SharpChat.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.29025.244 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpChat", "SharpChat\SharpChat.csproj", "{DDB24C19-B802-4C96-AC15-0449C6FC77F2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hamakaze", "Hamakaze\Hamakaze.csproj", "{6059200F-141C-42A5-AA3F-E38C9721AEC8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpChatTest", "SharpChatTest\SharpChatTest.csproj", "{6CD6DB9D-E0FF-4DEB-8E28-0C3EA6BA26B2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,10 +17,10 @@ Global {DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Release|Any CPU.Build.0 = Release|Any CPU - {6059200F-141C-42A5-AA3F-E38C9721AEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6059200F-141C-42A5-AA3F-E38C9721AEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6059200F-141C-42A5-AA3F-E38C9721AEC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6059200F-141C-42A5-AA3F-E38C9721AEC8}.Release|Any CPU.Build.0 = Release|Any CPU + {6CD6DB9D-E0FF-4DEB-8E28-0C3EA6BA26B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CD6DB9D-E0FF-4DEB-8E28-0C3EA6BA26B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CD6DB9D-E0FF-4DEB-8E28-0C3EA6BA26B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CD6DB9D-E0FF-4DEB-8E28-0C3EA6BA26B2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SharpChat/BanManager.cs b/SharpChat/BanManager.cs index 782c221..6155f21 100644 --- a/SharpChat/BanManager.cs +++ b/SharpChat/BanManager.cs @@ -144,19 +144,24 @@ namespace SharpChat { } public void RefreshFlashiiBans() { - FlashiiBan.GetList(bans => { - if(!bans.Any()) + FlashiiBan.GetList(SockChatServer.HttpClient).ContinueWith(x => { + if(x.IsFaulted) { + Logger.Write($@"Ban Refresh: {x.Exception}"); + return; + } + + if(!x.Result.Any()) return; lock(BanList) { - foreach(FlashiiBan fb in bans) { + foreach(FlashiiBan fb in x.Result) { if(!BanList.OfType().Any(x => x.UserId == fb.UserId)) Add(new BannedUser(fb)); if(!BanList.OfType().Any(x => x.Address.ToString() == fb.UserIP)) Add(new BannedIPAddress(fb)); } } - }, ex => Logger.Write($@"Ban Refresh: {ex}")); + }); } public IEnumerable All() { diff --git a/SharpChat/ChatContext.cs b/SharpChat/ChatContext.cs index 5e4ad95..4b999c5 100644 --- a/SharpChat/ChatContext.cs +++ b/SharpChat/ChatContext.cs @@ -26,7 +26,7 @@ namespace SharpChat { Channels = new ChannelManager(this); Events = new ChatEventManager(this); - BumpTimer = new Timer(e => FlashiiBump.Submit(Users.WithActiveConnections()), null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); + BumpTimer = new Timer(e => FlashiiBump.Submit(SockChatServer.HttpClient, Users.WithActiveConnections()), null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); } public void Update() { diff --git a/SharpChat/ChatEventManager.cs b/SharpChat/ChatEventManager.cs index 35bc3a3..53593a7 100644 --- a/SharpChat/ChatEventManager.cs +++ b/SharpChat/ChatEventManager.cs @@ -15,7 +15,7 @@ namespace SharpChat { public ChatEventManager(ChatContext context) { Context = context; - if (!Database.HasDatabase) + if (!Database.HasDatabase && !WebDatabase.IsAvailable) Events = new List(); } @@ -27,6 +27,9 @@ namespace SharpChat { lock(Events) Events.Add(evt); + if(WebDatabase.IsAvailable) + WebDatabase.LogEvent(evt); + if(Database.HasDatabase) Database.LogEvent(evt); } @@ -39,6 +42,9 @@ namespace SharpChat { lock (Events) Events.Remove(evt); + if(WebDatabase.IsAvailable) + WebDatabase.DeleteEvent(evt); + if (Database.HasDatabase) Database.DeleteEvent(evt); @@ -49,6 +55,9 @@ namespace SharpChat { if (seqId < 1) return null; + if(WebDatabase.IsAvailable) + return WebDatabase.GetEvent(seqId); + if (Database.HasDatabase) return Database.GetEvent(seqId); @@ -60,6 +69,9 @@ namespace SharpChat { } public IEnumerable GetTargetLog(IPacketTarget target, int amount = 20, int offset = 0) { + if(WebDatabase.IsAvailable) + return WebDatabase.GetEvents(target, amount, offset); + if (Database.HasDatabase) return Database.GetEvents(target, amount, offset).Reverse(); diff --git a/SharpChat/ChatUser.cs b/SharpChat/ChatUser.cs index 769542c..37735dc 100644 --- a/SharpChat/ChatUser.cs +++ b/SharpChat/ChatUser.cs @@ -94,7 +94,7 @@ namespace SharpChat { public ChatChannel CurrentChannel { get; private set; } public bool IsSilenced - => DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero; + => SilencedUntil != null && DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero; public bool HasSessions { get { diff --git a/SharpChat/Flashii/FlashiiAuth.cs b/SharpChat/Flashii/FlashiiAuth.cs index 7382bb3..3dd3ef0 100644 --- a/SharpChat/Flashii/FlashiiAuth.cs +++ b/SharpChat/Flashii/FlashiiAuth.cs @@ -1,7 +1,9 @@ -using Hamakaze; +using Microsoft.Win32.SafeHandles; using System; +using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; namespace SharpChat.Flashii { public class FlashiiAuthRequest { @@ -47,13 +49,15 @@ namespace SharpChat.Flashii { [JsonPropertyName(@"perms")] public ChatUserPermissions Permissions { get; set; } - public static void Attempt(FlashiiAuthRequest authRequest, Action onComplete, Action onError) { + public static async Task Attempt(HttpClient httpClient, FlashiiAuthRequest authRequest) { + if(httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); if(authRequest == null) throw new ArgumentNullException(nameof(authRequest)); #if DEBUG - if(authRequest.UserId >= 10000) { - onComplete(new FlashiiAuth { + if (authRequest.UserId >= 10000) + return new FlashiiAuth { Success = true, UserId = authRequest.UserId, Username = @"Misaka-" + (authRequest.UserId - 10000), @@ -61,21 +65,21 @@ namespace SharpChat.Flashii { Rank = 0, SilencedUntil = DateTimeOffset.MinValue, Permissions = ChatUserPermissions.SendMessage | ChatUserPermissions.EditOwnMessage | ChatUserPermissions.DeleteOwnMessage, - }); - return; - } + }; #endif - HttpRequestMessage hrm = new HttpRequestMessage(@"POST", FlashiiUrls.AUTH); - hrm.AddHeader(@"X-SharpChat-Signature", authRequest.Hash); - hrm.SetBody(authRequest.GetJSON()); - HttpClient.Send(hrm, (t, r) => { - try { - onComplete(JsonSerializer.Deserialize(r.GetBodyBytes())); - } catch(Exception ex) { - onError(ex); - } - }, (t, e) => onError(e)); + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, FlashiiUrls.AUTH) { + Content = new ByteArrayContent(authRequest.GetJSON()), + Headers = { + { @"X-SharpChat-Signature", authRequest.Hash }, + }, + }; + + using HttpResponseMessage response = await httpClient.SendAsync(request); + + return JsonSerializer.Deserialize( + await response.Content.ReadAsByteArrayAsync() + ); } } } diff --git a/SharpChat/Flashii/FlashiiBan.cs b/SharpChat/Flashii/FlashiiBan.cs index 56d411d..39b2257 100644 --- a/SharpChat/Flashii/FlashiiBan.cs +++ b/SharpChat/Flashii/FlashiiBan.cs @@ -1,8 +1,9 @@ -using Hamakaze; -using System; +using System; using System.Collections.Generic; +using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; namespace SharpChat.Flashii { public class FlashiiBan { @@ -20,21 +21,19 @@ namespace SharpChat.Flashii { [JsonPropertyName(@"username")] public string Username { get; set; } - public static void GetList(Action> onComplete, Action onError) { - if(onComplete == null) - throw new ArgumentNullException(nameof(onComplete)); - if(onError == null) - throw new ArgumentNullException(nameof(onError)); + public static async Task> GetList(HttpClient httpClient) { + if(httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - HttpRequestMessage hrm = new HttpRequestMessage(@"GET", FlashiiUrls.BANS); - hrm.AddHeader(@"X-SharpChat-Signature", STRING.GetSignedHash()); - HttpClient.Send(hrm, (t, r) => { - try { - onComplete(JsonSerializer.Deserialize>(r.GetBodyBytes())); - } catch(Exception ex) { - onError(ex); - } - }, (t, e) => onError(e)); + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, FlashiiUrls.BANS) { + Headers = { + { @"X-SharpChat-Signature", STRING.GetSignedHash() }, + }, + }; + + using HttpResponseMessage response = await httpClient.SendAsync(request); + + return JsonSerializer.Deserialize>(await response.Content.ReadAsByteArrayAsync()); } } } diff --git a/SharpChat/Flashii/FlashiiBump.cs b/SharpChat/Flashii/FlashiiBump.cs index 06c6168..5eafb2e 100644 --- a/SharpChat/Flashii/FlashiiBump.cs +++ b/SharpChat/Flashii/FlashiiBump.cs @@ -1,9 +1,10 @@ -using Hamakaze; -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; namespace SharpChat.Flashii { public class FlashiiBump { @@ -13,14 +14,16 @@ namespace SharpChat.Flashii { [JsonPropertyName(@"ip")] public string UserIP { get; set; } - public static void Submit(IEnumerable users) { + public static void Submit(HttpClient httpClient, IEnumerable users) { List bups = users.Where(u => u.HasSessions).Select(x => new FlashiiBump { UserId = x.UserId, UserIP = x.RemoteAddresses.First().ToString() }).ToList(); - if(bups.Any()) - Submit(bups); + if (bups.Any()) + Submit(httpClient, bups); } - public static void Submit(IEnumerable users) { + public static void Submit(HttpClient httpClient, IEnumerable users) { + if(httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); if(users == null) throw new ArgumentNullException(nameof(users)); if(!users.Any()) @@ -28,10 +31,17 @@ namespace SharpChat.Flashii { byte[] data = JsonSerializer.SerializeToUtf8Bytes(users); - HttpRequestMessage hrm = new HttpRequestMessage(@"POST", FlashiiUrls.BUMP); - hrm.AddHeader(@"X-SharpChat-Signature", data.GetSignedHash()); - hrm.SetBody(data); - HttpClient.Send(hrm, onError: (t, e) => Logger.Write($@"Flashii Bump Error: {e}")); + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, FlashiiUrls.BUMP) { + Content = new ByteArrayContent(data), + Headers = { + { @"X-SharpChat-Signature", data.GetSignedHash() }, + } + }; + + httpClient.SendAsync(request).ContinueWith(x => { + if(x.IsFaulted) + Logger.Write($@"Flashii Bump Error: {x.Exception}"); + }); } } } diff --git a/SharpChat/Program.cs b/SharpChat/Program.cs index 16ebfb5..34c815b 100644 --- a/SharpChat/Program.cs +++ b/SharpChat/Program.cs @@ -1,5 +1,7 @@ -using Hamakaze; +using SharpChat.Flashii; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; namespace SharpChat { @@ -17,14 +19,66 @@ namespace SharpChat { Console.WriteLine(@"============================================ DEBUG =="); #endif - HttpClient.Instance.DefaultUserAgent = @"SharpChat/0.9"; +#if DEBUG + Console.WriteLine(@"HOLD A KEY TO START A TEST NOW"); + Thread.Sleep(1000); + if (Console.KeyAvailable) + switch (Console.ReadKey(true).Key) { + case ConsoleKey.F: + TestMisuzuAuth(); + return; + } +#endif - Database.ReadConfig(); + WebDatabase.ReadConfig(); + if(!WebDatabase.IsAvailable) + Database.ReadConfig(); using ManualResetEvent mre = new ManualResetEvent(false); using SockChatServer scs = new SockChatServer(PORT); Console.CancelKeyPress += (s, e) => { e.Cancel = true; mre.Set(); }; mre.WaitOne(); } + +#if DEBUG + private static void TestMisuzuAuth() { + Console.WriteLine($@"Enter token found on {FlashiiUrls.BASE_URL}/login:"); + string[] token = Console.ReadLine().Split(new[] { '_' }, 2); + + System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(@"SharpChat"); + + FlashiiAuth authRes = FlashiiAuth.Attempt(httpClient, new FlashiiAuthRequest { + UserId = int.Parse(token[0]), Token = token[1], IPAddress = @"1.2.4.8" + }).GetAwaiter().GetResult(); + + if(authRes.Success) { + Console.WriteLine(@"Auth success!"); + Console.WriteLine($@" User ID: {authRes.UserId}"); + Console.WriteLine($@" Username: {authRes.Username}"); + Console.WriteLine($@" Colour: {authRes.ColourRaw:X8}"); + Console.WriteLine($@" Hierarchy: {authRes.Rank}"); + Console.WriteLine($@" Silenced: {authRes.SilencedUntil}"); + Console.WriteLine($@" Perms: {authRes.Permissions}"); + } else { + Console.WriteLine($@"Auth failed: {authRes.Reason}"); + return; + } + + Console.WriteLine(@"Bumping last seen..."); + FlashiiBump.Submit(httpClient, new[] { new ChatUser(authRes) }); + + Console.WriteLine(@"Fetching ban list..."); + IEnumerable bans = FlashiiBan.GetList(httpClient).GetAwaiter().GetResult(); + Console.WriteLine($@"{bans.Count()} BANS"); + foreach(FlashiiBan ban in bans) { + Console.WriteLine($@"BAN INFO"); + Console.WriteLine($@" User ID: {ban.UserId}"); + Console.WriteLine($@" Username: {ban.Username}"); + Console.WriteLine($@" IP Address: {ban.UserIP}"); + Console.WriteLine($@" Expires: {ban.Expires}"); + } + } +#endif } } diff --git a/SharpChat/SharpChat.csproj b/SharpChat/SharpChat.csproj index 6d2806a..a0a87ea 100644 --- a/SharpChat/SharpChat.csproj +++ b/SharpChat/SharpChat.csproj @@ -10,8 +10,4 @@ - - - - diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs index a9dc4f5..a643057 100644 --- a/SharpChat/SockChatServer.cs +++ b/SharpChat/SockChatServer.cs @@ -8,6 +8,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; +using System.Runtime.CompilerServices; using System.Text; namespace SharpChat { @@ -37,6 +39,8 @@ namespace SharpChat { public IWebSocketServer Server { get; } public ChatContext Context { get; } + public static HttpClient HttpClient { get; } + private IReadOnlyCollection Commands { get; } = new IChatCommand[] { new AFKCommand(), }; @@ -49,6 +53,12 @@ namespace SharpChat { return Sessions.FirstOrDefault(x => x.Connection == conn); } + static SockChatServer() { + // "fuck it" + HttpClient = new HttpClient(); + HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(@"SharpChat"); + } + public SockChatServer(ushort port) { Logger.Write("Starting Sock Chat server..."); @@ -162,11 +172,20 @@ namespace SharpChat { if(args.Length < 3 || !long.TryParse(args[1], out long aUserId)) break; - FlashiiAuth.Attempt(new FlashiiAuthRequest { + FlashiiAuth.Attempt(HttpClient, new FlashiiAuthRequest { UserId = aUserId, Token = args[2], IPAddress = sess.RemoteAddress.ToString(), - }, auth => { + }).ContinueWith(authTask => { + if(authTask.IsFaulted) { + Logger.Write($@"<{sess.Id}> Auth task fail: {authTask.Exception}"); + sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); + sess.Dispose(); + return; + } + + FlashiiAuth auth = authTask.Result; + if(!auth.Success) { Logger.Debug($@"<{sess.Id}> Auth fail: {auth.Reason}"); sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); @@ -214,10 +233,6 @@ namespace SharpChat { } Context.HandleJoin(aUser, Context.Channels.DefaultChannel, sess); - }, ex => { - Logger.Write($@"<{sess.Id}> Auth task fail: {ex}"); - sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); - sess.Dispose(); }); break; @@ -529,7 +544,7 @@ namespace SharpChat { } string createChanName = string.Join('_', parts.Skip(createChanHasHierarchy ? 2 : 1)); - ChatChannel createChan = new() { + ChatChannel createChan = new ChatChannel { Name = createChanName, IsTemporary = !user.Can(ChatUserPermissions.SetChannelPermanent), Rank = createChanHierarchy, @@ -816,14 +831,12 @@ namespace SharpChat { } ~SockChatServer() - => DoDispose(); + => Dispose(false); - public void Dispose() { - DoDispose(); - GC.SuppressFinalize(this); - } + public void Dispose() + => Dispose(true); - private void DoDispose() { + private void Dispose(bool disposing) { if(IsDisposed) return; IsDisposed = true; @@ -831,6 +844,10 @@ namespace SharpChat { Sessions?.Clear(); Server?.Dispose(); Context?.Dispose(); + HttpClient?.Dispose(); + + if(disposing) + GC.SuppressFinalize(this); } } } diff --git a/SharpChat/WebDatabase.cs b/SharpChat/WebDatabase.cs new file mode 100644 index 0000000..80e22d6 --- /dev/null +++ b/SharpChat/WebDatabase.cs @@ -0,0 +1,233 @@ +using SharpChat.Events; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SharpChat { + public static class WebDatabase { + private static string EndPoint = null; + + public static bool IsAvailable + => !string.IsNullOrWhiteSpace(EndPoint); + + public static void ReadConfig() { + if(!File.Exists(@"webdb.txt")) + return; + string[] config = File.ReadAllLines(@"webdb.txt"); + if(config.Length < 1 && !string.IsNullOrWhiteSpace(config[0])) + return; + Init(config[0]); + } + + public static void Init(string endPoint) { + EndPoint = endPoint; + } + + public static void Deinit() { + EndPoint = null; + } + + public static void LogEvent(IChatEvent evt) { + if(evt.SequenceId < 1) + evt.SequenceId = Database.GenerateId(); + + string sId = evt.SequenceId.ToString(); + string sCreated = evt.DateTime.ToUnixTimeSeconds().ToString(); + string sType = evt.GetType().FullName; + string sTarget = evt.Target.TargetName ?? string.Empty; + string sFlags = ((byte)evt.Flags).ToString(); + string sData = JsonSerializer.Serialize(evt, evt.GetType()); + string sSender = evt.Sender?.UserId < 1 ? string.Empty : evt.Sender.UserId.ToString(); + string sSenderName = evt.Sender?.Username.ToString() ?? string.Empty; + string sSenderColour = evt.Sender?.Colour.Raw.ToString() ?? string.Empty; + string sSenderRank = evt.Sender?.Rank.ToString() ?? string.Empty; + string sSenderNick = evt.Sender?.Nickname?.ToString() ?? string.Empty; + string sSenderPerms = evt.Sender == null ? string.Empty : ((int)evt.Sender.Permissions).ToString(); + string sHash = string.Join('#', @"crev", sId, sCreated, sType, sTarget, sFlags, sSender, sSenderColour, sSenderRank, sSenderPerms, sSenderName, sSenderNick, sData); + + MultipartFormDataContent mfdc = new(); + mfdc.Add(new StringContent(sId), @"i"); + mfdc.Add(new StringContent(sCreated), @"c"); + mfdc.Add(new StringContent(sType), @"t"); + mfdc.Add(new StringContent(sTarget), @"l"); + mfdc.Add(new StringContent(sFlags), @"f"); + mfdc.Add(new StringContent(sData), @"d"); + mfdc.Add(new StringContent(sSender), @"s"); + mfdc.Add(new StringContent(sSenderName), @"su"); + mfdc.Add(new StringContent(sSenderColour), @"sc"); + mfdc.Add(new StringContent(sSenderRank), @"sr"); + mfdc.Add(new StringContent(sSenderNick), @"sn"); + mfdc.Add(new StringContent(sSenderPerms), @"sp"); + + HttpRequestMessage request = new(HttpMethod.Post, EndPoint + @"?m=crev") { + Content = mfdc, + Headers = { + { @"X-SharpChat-Signature", sHash.GetSignedHash() }, + } + }; + + SockChatServer.HttpClient.SendAsync(request).ContinueWith(x => { + if(x.IsCompleted) { + if(x.Result.Headers.TryGetValues(@"X-SharpChat-Error", out IEnumerable values)) + Logger.Write($@"DBS - LogEvent: {values.FirstOrDefault()}"); + } + + if(x.IsFaulted) + Logger.Write($@"DBS - LogEvent: {x.Exception}"); + }).Wait(); + } + + public static void DeleteEvent(IChatEvent evt) { + string msgId = evt.SequenceId.ToString(); + + using HttpRequestMessage request = new(HttpMethod.Delete, EndPoint + @"?m=deev&i=" + msgId) { + Headers = { + { @"X-SharpChat-Signature", (@"deev#" + msgId).GetSignedHash() }, + } + }; + + SockChatServer.HttpClient.SendAsync(request).ContinueWith(x => { + if(x.IsCompleted) { + if(x.Result.Headers.TryGetValues(@"X-SharpChat-Error", out IEnumerable values)) + Logger.Write($@"DBS - DeleteEvent: {values.FirstOrDefault()}"); + } + + if(x.IsFaulted) + Logger.Write($@"DBS - DeleteEvent: {x.Exception}"); + }).Wait(); + } + + private class DatabaseRow { + [JsonPropertyName(@"i")] + public long EventId { get; set; } + + [JsonPropertyName(@"s")] + public long EventSenderId { get; set; } + + [JsonPropertyName(@"su")] + public string EventSenderName { get; set; } + + [JsonPropertyName(@"sc")] + public int EventSenderColour { get; set; } + + [JsonPropertyName(@"sr")] + public int EventSenderRank { get; set; } + + [JsonPropertyName(@"sn")] + public string EventSenderNick { get; set; } + + [JsonPropertyName(@"sp")] + public int EventSenderPerms { get; set; } + + [JsonPropertyName(@"c")] + public int EventCreated { get; set; } + + [JsonPropertyName(@"t")] + public string EventType { get; set; } + + [JsonPropertyName(@"l")] + public string EventTarget { get; set; } + + [JsonPropertyName(@"f")] + public byte EventFlags { get; set; } + + [JsonPropertyName(@"d")] + public string EventData { get; set; } + } + + private static IChatEvent ReadEvent(DatabaseRow row, IPacketTarget target = null) { + Type evtType = Type.GetType(row.EventType); + IChatEvent evt = JsonSerializer.Deserialize(row.EventData, evtType) as IChatEvent; + evt.SequenceId = row.EventId; + evt.Target = target; + evt.TargetName = target?.TargetName ?? row.EventTarget; + evt.Flags = (ChatMessageFlags)row.EventFlags; + evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(row.EventCreated); + + if(row.EventSenderId > 0) { + evt.Sender = new BasicUser { + UserId = row.EventSenderId, + Username = row.EventSenderName, + Colour = new ChatColour(row.EventSenderColour), + Rank = row.EventSenderRank, + Nickname = string.IsNullOrEmpty(row.EventSenderNick) ? null : row.EventSenderNick, + Permissions = (ChatUserPermissions)row.EventSenderPerms + }; + } + + return evt; + } + + public static IChatEvent GetEvent(long seqId) { + string sSeqId = seqId.ToString(); + + using HttpRequestMessage request = new(HttpMethod.Get, EndPoint + @"?m=geev&i=" + sSeqId) { + Headers = { + { @"X-SharpChat-Signature", (@"geev#" + sSeqId).GetSignedHash() }, + } + }; + + // jesus christ what is this + System.Runtime.CompilerServices.TaskAwaiter? awaiter = null; + HttpResponseMessage response = null; + + try { + awaiter = SockChatServer.HttpClient.SendAsync(request).GetAwaiter(); + response = awaiter.Value.GetResult(); + + byte[] garbage = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); + + return ReadEvent( + (DatabaseRow)JsonSerializer.Deserialize(garbage, typeof(DatabaseRow)) + ); + } catch(Exception ex) { + Logger.Write($@"DBS - GetEvent: {ex}"); + } finally { + if(awaiter.HasValue && awaiter.Value.IsCompleted) { + if(response != null && response.Headers.TryGetValues(@"X-SharpChat-Error", out IEnumerable values)) + Logger.Write($@"DBS - GetEvent: {values.FirstOrDefault()}"); + } + } + + return null; + } + + public static IEnumerable GetEvents(IPacketTarget target, int amount, int offset) { + List events = new(); + + using HttpRequestMessage request = new(HttpMethod.Get, EndPoint + @"?m=feev&l=" + target.TargetName + @"&a=" + amount + @"&o=" + offset) { + Headers = { + { @"X-SharpChat-Signature", string.Join('#', @"feev", amount, offset, target.TargetName).GetSignedHash() }, + } + }; + + System.Runtime.CompilerServices.TaskAwaiter? awaiter = null; + HttpResponseMessage response = null; + + try { + awaiter = SockChatServer.HttpClient.SendAsync(request).GetAwaiter(); + response = awaiter.Value.GetResult(); + + byte[] trash = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); + + IEnumerable rows = (IEnumerable)JsonSerializer.Deserialize(trash, typeof(IEnumerable)); + + foreach(DatabaseRow row in rows) + events.Add(ReadEvent(row, target)); + } catch(Exception ex) { + Logger.Write($@"DBS - GetEvents: {ex}"); + } finally { + if(awaiter.HasValue && awaiter.Value.IsCompleted) { + if(response != null && response.Headers.TryGetValues(@"X-SharpChat-Error", out IEnumerable values)) + Logger.Write($@"DBS - GetEvents: {values.FirstOrDefault()}"); + } + } + + return events; + } + } +}