diff --git a/Luski.net.sln b/Luski.net.sln new file mode 100755 index 0000000..4c1e359 --- /dev/null +++ b/Luski.net.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31717.71 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luski.net", "Luski.net\Luski.net.csproj", "{3DF9B870-51B3-4338-84EC-75E4B8802F0C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3DF9B870-51B3-4338-84EC-75E4B8802F0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DF9B870-51B3-4338-84EC-75E4B8802F0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DF9B870-51B3-4338-84EC-75E4B8802F0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DF9B870-51B3-4338-84EC-75E4B8802F0C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {49AFEA24-10EC-4D2C-B99C-C3E70124E443} + EndGlobalSection +EndGlobal diff --git a/Luski.net/API.cs b/Luski.net/API.cs new file mode 100644 index 0000000..0a26552 --- /dev/null +++ b/Luski.net/API.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.Enums; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net; + +public class API +{ + [JsonIgnore] + public MainServer MainServer { get; internal set; } + + public bool IsAnyServerLoggedin { get; internal set; } + + public const string DefaultVersion = "v1"; + + internal List Versions = new() + { + DefaultVersion + }; + + public IReadOnlyList SupportedVersions => Versions.AsReadOnly(); + internal List InternalServers { get; } = new(); + public IReadOnlyList LoadedServers => InternalServers.AsReadOnly(); + + internal List InternalFailedServers { get; } = new(); + public IReadOnlyList FailedServers => InternalFailedServers.AsReadOnly(); + + private static HttpResponseMessage GetFromServer(string Domain, string ApiVersion, bool Secure, string Path, CancellationToken CancellationToken, params KeyValuePair[] Headers) + { + using HttpClient web = new(); + web.Timeout = TimeSpan.FromSeconds(10); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + return web.GetAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", cancellationToken: CancellationToken).Result; + } + private static Task GetFromServer(string Domain, string ApiVersion, bool Secure, string Path, JsonTypeInfo Type, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tresult : STC, new() + { + HttpResponseMessage ServerResponce = GetFromServer(Domain, ApiVersion, Secure, Path, CancellationToken, Headers); + Tresult temp = new(); + string raw = ServerResponce.Content.ReadAsStringAsync(CancellationToken).Result; + try + { + temp = JsonSerializer.Deserialize(raw, Type)!; + } + catch (Exception e) + { + Console.WriteLine("JSON parse failed for the following data as type {0}\n{1}", temp.GetType(), raw); + } + if (temp is null) return Task.FromResult(new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }); + return Task.FromResult(temp); + } + + public Task GetServerInfo(string Domain, string Version = DefaultVersion, + bool Secure = true) + { + ServerInfoSTC? si = GetFromServer(Domain, Version, Secure, "socketserver", ServerInfoSTCContext.Default.ServerInfoSTC, CancellationToken.None).Result; + if (si is null) throw new Exception("Bad Response"); + return Task.FromResult(si); + } + + public Task TryGetServerInfo([NotNullWhen(true)]out ServerInfoSTC? si, string Domain, string Version = DefaultVersion, + bool Secure = true) + { + + try + { + si = GetServerInfo(Domain, Version, Secure).Result; + return Task.FromResult(true); + } + catch (Exception e) + { + si = null; + return Task.FromResult(false); + } + } + + public Task TryGetPublicServer(out PublicServer Server, string Domain, string Version = DefaultVersion, + bool Secure = true, bool GenerateEncryption = true, bool LogConsole = false) + { + try + { + Task result = GetPublicServer(Domain, Version, Secure, GenerateEncryption, LogConsole); + Task.WaitAll(result); + Server = result.Result; + return Task.FromResult(true); + } + catch (Exception e) + { + if (!e.Message.Contains("Connection refused")) Console.WriteLine(e); + Server = null!; + return Task.FromResult(false); + } + } + + + public async Task GetPublicServer(string Domain, string Version = DefaultVersion, bool Secure = true, bool GenerateEncryption = true, bool LogConsole = false) + { + PublicServer s; + try + { + IEnumerable isl = InternalServers.Where(a => (a.Domain == Domain && a.ApiVersion == Version)); + if (isl.Any()) return isl.First(); + s = await PublicServer.GetServer(InternalFailedServers, Domain, Version, Secure, GenerateEncryption, LogConsole); + } + catch (Exception e) + { + Console.WriteLine("Failed to connect to public server '{0}' using API {1}. No alternate server was found.", Domain, Version); + throw; + } + + string? f = s.Storage.GetStorageDirectory(StorageDirectory.StorageInfo) + "token"; + if (File.Exists(f)) + { + f = File.ReadAllText(f); + } + else f = null; + + try + { + if (f is not null) + { + bool b = await s.LoginViaToken(f); + if (b) + { + IsAnyServerLoggedin = true; + } + else + { + s.Token = null; + s.Error = null; + } + } + } + catch + { + } + + InternalServers.Add(s); + return s; + } + + public MainServer GetMainServer(string Domain, string Version = DefaultVersion) + { + DateTime dt = DateTime.UtcNow; + Console.WriteLine("Conecting to main server '{0}' using API {1}.", Domain, Version); + MainServer = new(Domain, Version) + { + ServerType = ServerType.Main + }; + Console.WriteLine("Connected to main server '{0}' using API {1} in {2}.", Domain, Version, DateTime.UtcNow.Subtract(dt).ToString("g")); + return MainServer; + } +} \ No newline at end of file diff --git a/Luski.net/Classes/RoleComparer.cs b/Luski.net/Classes/RoleComparer.cs new file mode 100644 index 0000000..78405dc --- /dev/null +++ b/Luski.net/Classes/RoleComparer.cs @@ -0,0 +1,13 @@ +using System.Collections; +using System.Collections.Generic; +using Luski.net.Structures.Public; + +namespace Luski.net.Classes; + +public class RoleComparer : IComparer +{ + public int Compare(Role x, Role y) + { + return y.Index - x.Index; + } +} \ No newline at end of file diff --git a/Luski.net/ClientEncryption.cs b/Luski.net/ClientEncryption.cs new file mode 100755 index 0000000..61b358f --- /dev/null +++ b/Luski.net/ClientEncryption.cs @@ -0,0 +1,458 @@ +/*using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace Luski.net +{ + public static class ClientEncryption + { + internal static string pw = ""; + + public static class File + {/* + internal static void SetOfflineKey(string key) + { + MakeFile("Server.GetKeyFilePath", pw); + LuskiDataFile? fileLayout = JsonSerializer.Deserialize(FileString("Server.GetKeyFilePath", pw)); + fileLayout.OfflineKey = key; + fileLayout.Save("Server.GetKeyFilePath", pw); + } + + + + internal static string? GetOfflineKey() + { + MakeFile("Server.GetKeyFilePath", pw); + LuskiDataFile? fileLayout = JsonSerializer.Deserialize(FileString("Server.GetKeyFilePath", pw)); + return fileLayout?.OfflineKey; + } + + public static LuskiDataFile GetFile() + { + MakeFile("Server.GetKeyFilePath", pw); + return JsonSerializer.Deserialize(FileString("Server.GetKeyFilePath", pw))!; + } + + private static string FileString(string path, string password) + { + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + byte[] salt = new byte[100]; + FileStream fsCrypt = new(path, FileMode.Open); + fsCrypt.Read(salt, 0, salt.Length); + RijndaelManaged AES = new() + { + KeySize = 256, + BlockSize = 128 + }; + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + AES.Key = key.GetBytes(AES.KeySize / 8); + AES.IV = key.GetBytes(AES.BlockSize / 8); + AES.Padding = PaddingMode.PKCS7; + AES.Mode = CipherMode.CFB; + CryptoStream cs = new(fsCrypt, AES.CreateDecryptor(), CryptoStreamMode.Read); + MemoryStream fsOut = new(); + int read; + byte[] buffer = new byte[1048576]; + try + { + while ((read = cs.Read(buffer, 0, buffer.Length)) > 0) + { + fsOut.Write(buffer, 0, read); + } + } + catch (CryptographicException ex_CryptographicException) + { + Console.WriteLine("CryptographicException error: " + ex_CryptographicException.Message); + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + fsOut.Seek(0, SeekOrigin.Begin); + using BinaryReader reader = new(fsOut); + byte[] by = reader.ReadBytes((int)fsOut.Length); + fsOut.Close(); + fsCrypt.Close(); + return Encoding.UTF8.GetString(by); + } + + public static class Channels + { + private static string GetKey(long channel) + { + LuskiDataFile? fileLayout; + IEnumerable? lis; + try + { +#pragma warning disable CS8603 // Possible null reference return. + // if (channel == 0) return myPrivateKey; +#pragma warning restore CS8603 // Possible null reference return. + MakeFile("Server.GetKeyFilePath", pw); + fileLayout = JsonSerializer.Deserialize(FileString("Server.GetKeyFilePath", pw)); + lis = fileLayout?.channels?.Where(s => s.id == channel); + if (lis?.Count() > 0) + { + return lis.First().key; + } + throw new Exception("You dont have a key for that channel"); + } + finally + { + fileLayout = null; + lis = null; + } + } + + internal static string GetKeyBranch(long channel) + { + LuskiDataFile? fileLayout; + IEnumerable? lis; + try + { +#pragma warning disable CS8603 // Possible null reference return. + // if (channel == 0) return myPrivateKey; +#pragma warning restore CS8603 // Possible null reference return. + MakeFile("Server.GetKeyFilePathBr(branch.ToString())", pw); + fileLayout = JsonSerializer.Deserialize(FileString("", pw)); + lis = fileLayout?.channels?.Where(s => s.id == channel); + if (lis?.Count() > 0) + { + return lis.First().key; + } + throw new Exception("You dont have a key for that channel"); + } + finally + { + fileLayout = null; + lis = null; + } + } + + public static void AddKey(long channel, string key) + { + MakeFile("Server.GetKeyFilePath", pw); + LuskiDataFile? fileLayout = JsonSerializer.Deserialize(FileString("Server.GetKeyFilePath", pw)); + fileLayout?.Addchannelkey(channel, key); + fileLayout?.Save("Server.GetKeyFilePath", pw); + } + } + + private static void MakeFile(string dir, string password) + { + if (!System.IO.File.Exists(dir)) + { + LuskiDataFile? l = JsonSerializer.Deserialize("{\"channels\":[]}"); + l?.Save(dir, password); + } + } + + public class LuskiDataFile + { + public static LuskiDataFile GetDataFile(string path, string password) + { + MakeFile(path, password); + return JsonSerializer.Deserialize(FileString(path, password)); + } + + internal static LuskiDataFile GetDefualtDataFile() + { + return GetDataFile("Server.GetKeyFilePath", pw); + } + + public ChannelLayout[]? channels { get; set; } = default!; + + public string? OfflineKey { get; set; } = default!; + + public void Save(string file, string password) + { + byte[] salt = new byte[100]; + RandomNumberGenerator? provider = RandomNumberGenerator.Create(); + provider.GetBytes(salt); + FileStream fsCrypt = new(file, FileMode.Create); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + RijndaelManaged AES = new() + { + KeySize = 256, + BlockSize = 128, + Padding = PaddingMode.PKCS7 + }; + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + AES.Key = key.GetBytes(AES.KeySize / 8); + AES.IV = key.GetBytes(AES.BlockSize / 8); + AES.Mode = CipherMode.CFB; + fsCrypt.Write(salt, 0, salt.Length); + CryptoStream cs = new(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write); + string tempp = JsonSerializer.Serialize(this); + MemoryStream fsIn = new(Encoding.UTF8.GetBytes(tempp)); + byte[] buffer = new byte[1048576]; + int read; + try + { + while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0) + { + cs.Write(buffer, 0, read); + } + fsIn.Close(); + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + finally + { + cs.Close(); + fsCrypt.Close(); + } + } + + public void Addchannelkey(long chan, string Key) + { + List? chans = channels?.ToList(); + if (chans is null) chans = new(); + if (!(chans?.Where(s => s.id == chan).Count() > 0)) + { + ChannelLayout l = new() + { + id = chan, + key = Key + }; + chans?.Add(l); + channels = chans?.ToArray(); + } + else + { + chans.Remove(chans.Where(s => s.id == chan).First()); + ChannelLayout l = new() + { + id = chan, + key = Key + }; + chans?.Add(l); + channels = chans?.ToArray(); + } + } + } + + public class ChannelLayout + { + public long id { get; set; } = default!; + public string key { get; set; } = default!; + } + } +public class AES + { + public static string Encrypt(string path, string Password) + { + string p = Path.GetTempFileName(); + byte[] salt = RandomNumberGenerator.GetBytes(100); + byte[] passwordBytes = Encoding.UTF8.GetBytes(Password); + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + byte[] data = System.IO.File.ReadAllBytes(path); + + using Aes aesAlg = Aes.Create(); + aesAlg.KeySize = 256; + aesAlg.BlockSize = 128; + aesAlg.Padding = PaddingMode.PKCS7; + aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8); + aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8); + + ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); + + using FileStream msEncrypt = new(p, FileMode.Open); + msEncrypt.Write(salt, 0, salt.Length); + using CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write); + csEncrypt.Write(data, 0, data.Length); + csEncrypt.Dispose(); + msEncrypt.Dispose(); + return p; + + + + + string p = Path.GetTempFileName(); + byte[] salt = new byte[100]; + RNGCryptoServiceProvider provider = new(); + provider.GetBytes(salt); + FileStream fsCrypt = new(p, FileMode.Open); + byte[] passwordBytes = Encoding.UTF8.GetBytes(Password); + Aes AES = Aes.Create(); + AES.KeySize = 256; + AES.BlockSize = 128; + AES.Padding = PaddingMode.PKCS7; + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + AES.Key = key.GetBytes(AES.KeySize / 8); + AES.IV = key.GetBytes(AES.BlockSize / 8); + AES.Mode = CipherMode.CFB; + fsCrypt.Write(salt, 0, salt.Length); + key.Dispose(); + CryptoStream cs = new(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write); + FileStream fsIn = new(path, FileMode.Open); + try + { + FileInfo FI = new(path); + byte[] buffer = new byte[FI.Length]; + int read; + while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0) + { + cs.Write(buffer, 0, read); + } + } + catch (OutOfMemoryException ex) + { + throw new Exception("Buffer", ex); + } + fsIn.Close(); + fsIn.Dispose(); + cs.Close(); + cs.Dispose(); + fsCrypt.Close(); + fsCrypt.Dispose(); + NewPath = p; + } + + public static void Decrypt(byte[] data, string Password, string File) + { + byte[] salt = new byte[100]; + using MemoryStream fsCrypt = new(data); + fsCrypt.Read(salt, 0, salt.Length); + byte[] passwordBytes = Encoding.UTF8.GetBytes(Password); + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + byte[] decrypted = new byte[data.Length - salt.Length]; + + using Aes aesAlg = Aes.Create(); + aesAlg.KeySize = 256; + aesAlg.BlockSize = 128; + aesAlg.Padding = PaddingMode.PKCS7; + aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8); + aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8); + + ICryptoTransform encryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); + + using CryptoStream csEncrypt = new(fsCrypt, encryptor, CryptoStreamMode.Read); + FileStream fsOut = new(File, FileMode.Create); + int read; + byte[] buffer = new byte[data.Length]; + while ((read = csEncrypt.Read(buffer, 0, buffer.Length)) > 0) + { + fsOut.Write(buffer, 0, read); + } + csEncrypt.Dispose(); + fsCrypt.Dispose(); + fsOut.Dispose(); + } + } + + public static byte[] Hash(byte[] data) + { + return SHA256.Create().ComputeHash(data); + } + + internal static byte[] Encrypt(string data) + { + return Encrypt(data, ServerPublicKey); + } + + internal static byte[] Encrypt(byte[] data) + { + return Encrypt(data, ServerPublicKey); + } + + internal static byte[] Encrypt(string data, string key, bool multithread = false) + { + return Encrypt(Encoder.GetBytes(data), key, multithread); + } + + internal static byte[] Encrypt(byte[] data, string key, bool multithread = false) + { + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(key); + int size = ((rsa.KeySize - 384) / 8) + 7; + double x = data.Length / (double)size; + int bbb = int.Parse(x.ToString().Split('.')[0]); + if (x.ToString().Contains('.')) bbb++; + byte[]? datasplitout = Array.Empty(); + if (multithread) + { + byte[][]? decccc = Array.Empty(); + Array.Resize(ref decccc, bbb); + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * Server.Percent) * 2.0)); + if (num == 0) num = 1; + Parallel.For(0, bbb, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + decccc[i] = rsa.Encrypt(data.Skip(i * size).Take(size).ToArray(), true); + }); + foreach (byte[] dataa in decccc) + { + datasplitout = Combine(datasplitout, dataa); + } + } + else + { + for (int i = 0; i < bbb; i++) + { + datasplitout = Combine(datasplitout, rsa.Encrypt(data.Skip(i * size).Take(size).ToArray(), false)); + } + } + return datasplitout; + } + + private static byte[] Combine(byte[] first, byte[] second) + { + byte[]? bytes = new byte[first.Length + second.Length]; + Buffer.BlockCopy(first, 0, bytes, 0, first.Length); + Buffer.BlockCopy(second, 0, bytes, first.Length, second.Length); + return bytes; + } + + internal static byte[] Decrypt(byte[] EncryptedText, bool multithread = false) + { + return Decrypt(EncryptedText, myPrivateKey, multithread); + } + + internal static byte[] Decrypt(byte[]? EncryptedText, string? key, bool multithread = false) + { + if (key is null) throw new ArgumentNullException(nameof(key)); + if (EncryptedText is null) throw new ArgumentNullException(nameof(EncryptedText)); + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(key); + int size = rsa.KeySize / 8; + double x = EncryptedText.Length / (double)size; + int bbb = int.Parse(x.ToString().Split('.')[0]); + if (x.ToString().Contains('.')) bbb++; + byte[]? datasplitout = Array.Empty(); + if (multithread) + { + byte[][]? decccc = Array.Empty(); + Array.Resize(ref decccc, bbb); + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * Server.Percent) * 2.0)); + if (num == 0) num = 1; + Parallel.For(0, bbb, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + decccc[i] = rsa.Decrypt(EncryptedText.Skip(i * size).Take(size).ToArray(), true); + }); + foreach (byte[] data in decccc) + { + datasplitout = Combine(datasplitout, data); + } + } + else + { + for (int i = 0; i < bbb; i++) + { + datasplitout = Combine(datasplitout, rsa.Decrypt(EncryptedText.Skip(i * size).Take(size).ToArray(), false)); + } + } + return datasplitout; + } + } +}*/ diff --git a/Luski.net/Converters.cs b/Luski.net/Converters.cs new file mode 100644 index 0000000..102371b --- /dev/null +++ b/Luski.net/Converters.cs @@ -0,0 +1,5 @@ +namespace Luski.net; + +public static class Converters +{ +} \ No newline at end of file diff --git a/Luski.net/Enums/CacheMode.cs b/Luski.net/Enums/CacheMode.cs new file mode 100644 index 0000000..6fc1243 --- /dev/null +++ b/Luski.net/Enums/CacheMode.cs @@ -0,0 +1,8 @@ +namespace Luski.net.Enums; + +public enum CacheMode : byte +{ + None, + Encrypted, + Unencrypted +} \ No newline at end of file diff --git a/Luski.net/Enums/ConnectionStatus.cs b/Luski.net/Enums/ConnectionStatus.cs new file mode 100644 index 0000000..bf6993f --- /dev/null +++ b/Luski.net/Enums/ConnectionStatus.cs @@ -0,0 +1,10 @@ +namespace Luski.net.Enums; + +public enum ConnectionStatus +{ + FailedToConnect, + CommunicationError, + FailedToLogin, + ConnectedNotLoggedIn, + LoggedIn, +} \ No newline at end of file diff --git a/Luski.net/Enums/DataType.cs b/Luski.net/Enums/DataType.cs new file mode 100644 index 0000000..6a5189a --- /dev/null +++ b/Luski.net/Enums/DataType.cs @@ -0,0 +1,18 @@ +namespace Luski.net.Enums; + +public enum DataType +{ + Message_Create, + Status_Update, + Friend_Request_Result, + Friend_Request, + Change_Channel, + Join_Call, + Leave_Call, + Call_Info, + Call_Data, + Login, + Error, + Key_Exchange, + MAX +} \ No newline at end of file diff --git a/Luski.net/Enums/Main/ChannelType.cs b/Luski.net/Enums/Main/ChannelType.cs new file mode 100755 index 0000000..09fe9c0 --- /dev/null +++ b/Luski.net/Enums/Main/ChannelType.cs @@ -0,0 +1,7 @@ +namespace Luski.net.Enums.Main; + +public enum ChannelType : short +{ + DM, + GROUP, +} diff --git a/Luski.net/Enums/Main/FriendStatus.cs b/Luski.net/Enums/Main/FriendStatus.cs new file mode 100755 index 0000000..75aad9b --- /dev/null +++ b/Luski.net/Enums/Main/FriendStatus.cs @@ -0,0 +1,9 @@ +namespace Luski.net.Enums.Main; + +public enum FriendStatus +{ + NotFriends, + Friends, + PendingOut, + PendingIn +} diff --git a/Luski.net/Enums/Main/UserFlag.cs b/Luski.net/Enums/Main/UserFlag.cs new file mode 100755 index 0000000..aa1378a --- /dev/null +++ b/Luski.net/Enums/Main/UserFlag.cs @@ -0,0 +1,11 @@ +using System; + +namespace Luski.net.Enums.Main; + +[Flags] +public enum UserFlag : short +{ + Dev = 0b_001, + Early = 0b_010, + Tester = 0b_100 +} diff --git a/Luski.net/Enums/ServerType.cs b/Luski.net/Enums/ServerType.cs new file mode 100644 index 0000000..cbace6a --- /dev/null +++ b/Luski.net/Enums/ServerType.cs @@ -0,0 +1,9 @@ +namespace Luski.net.Enums; + +public enum ServerType : byte +{ + Public = 0, + Main = 1, + PublicSub = 2, + MainSub = 3 +} \ No newline at end of file diff --git a/Luski.net/Enums/StorageDirectory.cs b/Luski.net/Enums/StorageDirectory.cs new file mode 100644 index 0000000..f524ff1 --- /dev/null +++ b/Luski.net/Enums/StorageDirectory.cs @@ -0,0 +1,15 @@ +namespace Luski.net.Enums; + +public enum StorageDirectory : byte +{ + ServerInfo, + ServerAssets, + ChannelKeys, + ServerKeys, + Avatars, + ChannelIcons, + Messages, + StorageInfo, + Files, + ProfileAvatars +} \ No newline at end of file diff --git a/Luski.net/Exceptions.cs b/Luski.net/Exceptions.cs new file mode 100755 index 0000000..8b26354 --- /dev/null +++ b/Luski.net/Exceptions.cs @@ -0,0 +1,30 @@ +using System; + +namespace Luski.net +{ + public class Exceptions + { + + [Serializable] + public class MissingEventException : Exception + { + public string EventName; + public MissingEventException(string Event) : base(Event) + { + EventName = Event; + } + } + + + [Serializable] + public class NotConnectedException : Exception + { + public NotConnectedException(object sender, string message) : base(message) + { + Sender = sender; + } + + public object Sender { get; } + } + } +} diff --git a/Luski.net/Interfaces/IAppUser.cs b/Luski.net/Interfaces/IAppUser.cs new file mode 100644 index 0000000..f49a1e6 --- /dev/null +++ b/Luski.net/Interfaces/IAppUser.cs @@ -0,0 +1,10 @@ +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.Interfaces; + +public interface IAppUser : IUser +{ + public ErrorCode? Error { get; } + public string? ErrorMessage { get; } + public string Username { get; } +} \ No newline at end of file diff --git a/Luski.net/Interfaces/IServer.cs b/Luski.net/Interfaces/IServer.cs new file mode 100644 index 0000000..908bb89 --- /dev/null +++ b/Luski.net/Interfaces/IServer.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.JsonTypes.BaseTypes; +using Luski.net.Structures; +using Luski.net.Structures.Public; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Interfaces; + +public interface IServer +{ + public IAppUser IAppUser { get; } + public string Cache { get; } + public void SendServer(Tvalue Payload, JsonTypeInfo jsonTypeInfo) where Tvalue : IncomingWSS; + + public HttpResponseMessage GetFromServer(string Path, CancellationToken CancellationToken, + params KeyValuePair[] Headers); + + public Task GetFromServer(string Path, string File, CancellationToken CancellationToken, + params KeyValuePair[] Headers); + + public Task GetFromServer(string Path, JsonTypeInfo Type, + CancellationToken CancellationToken, params KeyValuePair[] Headers) + where Tresult : STC, new(); + + public Task SendServer(string Path, Tvalue Payload, + JsonTypeInfo jsonTypeInfo, JsonTypeInfo ReturnjsonTypeInfo, + CancellationToken CancellationToken, params KeyValuePair[] Headers) + where Tvalue : IWebRequest where Tresult : STC, new(); + + public Task SendServer(string Path, string File, JsonTypeInfo ReturnjsonTypeInfo, + CancellationToken CancellationToken, params KeyValuePair[] Headers) + where Tresult : STC, new(); + +} \ No newline at end of file diff --git a/Luski.net/Interfaces/IServerEvent.cs b/Luski.net/Interfaces/IServerEvent.cs new file mode 100644 index 0000000..fbf895f --- /dev/null +++ b/Luski.net/Interfaces/IServerEvent.cs @@ -0,0 +1,6 @@ +namespace Luski.net.Interfaces; + +public interface IServerEvent +{ + +} \ No newline at end of file diff --git a/Luski.net/Interfaces/IUser.cs b/Luski.net/Interfaces/IUser.cs new file mode 100755 index 0000000..bd17814 --- /dev/null +++ b/Luski.net/Interfaces/IUser.cs @@ -0,0 +1,29 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.Structures; +using Luski.net.Structures.Public; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.Interfaces; + +/// +/// Represents the curent user +/// +public interface IUser +{ + /// + /// The current Id of the user + /// + long Id { get; } + /// + /// The current status of the user + /// + UserStatus Status { get; } + /// + /// Gets the current user keys + /// + /// + Task GetUserKeys(CancellationToken CancellationToken); + +} diff --git a/Luski.net/Interfaces/IWebRequest.cs b/Luski.net/Interfaces/IWebRequest.cs new file mode 100644 index 0000000..8ebc582 --- /dev/null +++ b/Luski.net/Interfaces/IWebRequest.cs @@ -0,0 +1,6 @@ +namespace Luski.net.Interfaces; + +public interface IWebRequest +{ + +} \ No newline at end of file diff --git a/Luski.net/JsonRequest.cs b/Luski.net/JsonRequest.cs new file mode 100755 index 0000000..4a898e3 --- /dev/null +++ b/Luski.net/JsonRequest.cs @@ -0,0 +1,28 @@ +using Luski.net.Enums; +using System; + +namespace Luski.net +{ + internal static class JsonRequest + { + internal static string SendCallData(byte[] Data, long channel) + { + return $"{{\"data\": \"{Convert.ToBase64String(Data)}\", \"id\": {channel}}}"; + } + + internal static string JoinCall(long Channel) + { + return $"{{\"id\": {Channel}}}"; + } + + internal static string Send(DataType Request, string Data) + { + return $"{{\"type\": {(int)Request}, \"data\": {Data}}}"; + } + + internal static string FriendRequestResult(long User, bool Result) + { + return $"{{\"id\": {User},\"result\": {Result.ToString().ToLower()}}}"; + } + } +} diff --git a/Luski.net/JsonTypes/BaseTypes/HTTPRequest.cs b/Luski.net/JsonTypes/BaseTypes/HTTPRequest.cs new file mode 100755 index 0000000..f54a522 --- /dev/null +++ b/Luski.net/JsonTypes/BaseTypes/HTTPRequest.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using Luski.net.Enums; +using Luski.net.Interfaces; + +namespace Luski.net.JsonTypes.BaseTypes; + +internal class HTTPRequest : IWebRequest +{ + [JsonPropertyName("data_type")] + [JsonInclude] + public DataType Type { get; set; } = default!; +} diff --git a/Luski.net/JsonTypes/BaseTypes/IncomingWSS.cs b/Luski.net/JsonTypes/BaseTypes/IncomingWSS.cs new file mode 100755 index 0000000..84b380d --- /dev/null +++ b/Luski.net/JsonTypes/BaseTypes/IncomingWSS.cs @@ -0,0 +1,24 @@ +using Luski.net.Enums; +using System.Text.Json.Serialization; + +namespace Luski.net.JsonTypes.BaseTypes; + +public class IncomingWSS +{ + [JsonPropertyName("type")] + [JsonInclude] + public DataType? Type { get; set; } = DataType.Login!; + [JsonPropertyName("error")] + [JsonInclude] + public string Error { get; set; } = default!; +} + +[JsonSerializable(typeof(IncomingWSS))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, + WriteIndented = false)] +public partial class IncomingWSSContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/FriendRequestResult.cs b/Luski.net/JsonTypes/FriendRequestResult.cs new file mode 100755 index 0000000..5d39096 --- /dev/null +++ b/Luski.net/JsonTypes/FriendRequestResult.cs @@ -0,0 +1,28 @@ +using Luski.net.JsonTypes.BaseTypes; +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.JsonTypes; + +internal class FriendRequestResult : STC +{ + [JsonPropertyName("channel")] + [JsonInclude] + public long? Channel { get; set; } + [JsonPropertyName("id")] + [JsonInclude] + public long? Id { get; set; } + [JsonPropertyName("result")] + [JsonInclude] + public bool? Result { get; set; } +} + +[JsonSerializable(typeof(FriendRequestResult))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class FriendRequestResultContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/HTTP/FriendRequest.cs b/Luski.net/JsonTypes/HTTP/FriendRequest.cs new file mode 100755 index 0000000..ce6fc6e --- /dev/null +++ b/Luski.net/JsonTypes/HTTP/FriendRequest.cs @@ -0,0 +1,25 @@ +using Luski.net.JsonTypes.BaseTypes; +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; + +namespace Luski.net.JsonTypes.HTTP; + +internal class FriendRequest : CTS +{ + [JsonPropertyName("code")] + [JsonInclude] + public string code { get; set; } = default!; + [JsonPropertyName("id")] + [JsonInclude] + public long Id { get; set; } = default!; +} + +[JsonSerializable(typeof(FriendRequest))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class FriendRequestContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/HTTP/FriendRequestResultOut.cs b/Luski.net/JsonTypes/HTTP/FriendRequestResultOut.cs new file mode 100755 index 0000000..d5cd88a --- /dev/null +++ b/Luski.net/JsonTypes/HTTP/FriendRequestResultOut.cs @@ -0,0 +1,25 @@ +using Luski.net.JsonTypes.BaseTypes; +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; + +namespace Luski.net.JsonTypes.HTTP; + +internal class FriendRequestResultOut : CTS +{ + [JsonPropertyName("id")] + [JsonInclude] + public long? Id { get; set; } + [JsonPropertyName("result")] + [JsonInclude] + public bool? Result { get; set; } +} + +[JsonSerializable(typeof(FriendRequestResultOut))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class FriendRequestResultOutContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/KeyExchange.cs b/Luski.net/JsonTypes/KeyExchange.cs new file mode 100755 index 0000000..ff25400 --- /dev/null +++ b/Luski.net/JsonTypes/KeyExchange.cs @@ -0,0 +1,11 @@ +namespace Luski.net.JsonTypes +{ + internal class KeyExchange + { + public long channel { get; set; } = default!; + public long id { get; set; } = default!; + public string key { get; set; } = default!; + + public long? to { get; set; } = default!; + } +} diff --git a/Luski.net/JsonTypes/LocalServerInfo.cs b/Luski.net/JsonTypes/LocalServerInfo.cs new file mode 100644 index 0000000..b124597 --- /dev/null +++ b/Luski.net/JsonTypes/LocalServerInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.Shared; + +namespace Luski.net.JsonTypes; + +public class LocalServerInfo +{ + [JsonInclude] + [JsonPropertyName("alternate_servers")] + public ServerData[] AlternateServers { get; set; } = Array.Empty(); + [JsonInclude] + [JsonPropertyName("picture_type")] + public PictureType PictureType { get; set; } + [JsonInclude] + [JsonPropertyName("name")] + public string Name { get; set; } = default!; + [JsonInclude] + [JsonPropertyName("description")] + public string Description { get; set; } = default!; +} + +[JsonSerializable(typeof(LocalServerInfo))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class LocalServerInfoContext : JsonSerializerContext; \ No newline at end of file diff --git a/Luski.net/JsonTypes/OfflineKeyData.cs b/Luski.net/JsonTypes/OfflineKeyData.cs new file mode 100755 index 0000000..3aaf56b --- /dev/null +++ b/Luski.net/JsonTypes/OfflineKeyData.cs @@ -0,0 +1,20 @@ +using Luski.net.JsonTypes.BaseTypes; +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.JsonTypes; + +internal class OfflineKeyData : STC +{ + public KeyExchange[]? keys { get; internal set; } = default!; +} + +[JsonSerializable(typeof(OfflineKeyData))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class OfflineKeyDataContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/ServerStorageInfo.cs b/Luski.net/JsonTypes/ServerStorageInfo.cs new file mode 100644 index 0000000..fc87170 --- /dev/null +++ b/Luski.net/JsonTypes/ServerStorageInfo.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; +using Luski.net.Enums; + +namespace Luski.net.JsonTypes; + +public class ServerStorageInfo +{ + [JsonInclude] + [JsonPropertyName("cache_mode")] + public CacheMode CacheMode { get; set; } = CacheMode.Encrypted; + [JsonInclude] + [JsonPropertyName("storage_id")] + public long StorageID { get; set; } = default!; + [JsonInclude] + [JsonPropertyName("prevent_deletion")] + public bool DontDelete { get; set; } = false; +} + +[JsonSerializable(typeof(ServerStorageInfo))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = true)] +internal partial class ServerStorageInfoContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/Luski.net/JsonTypes/StatusUpdate.cs b/Luski.net/JsonTypes/StatusUpdate.cs new file mode 100755 index 0000000..e274f78 --- /dev/null +++ b/Luski.net/JsonTypes/StatusUpdate.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.JsonTypes; + +internal class StatusUpdate +{ + [JsonInclude] + [JsonPropertyName("id")] + public long id { get; set; } = default!; + [JsonInclude] + [JsonPropertyName("before")] + public UserStatus before { get; set; } = default!; + [JsonInclude] + [JsonPropertyName("after")] + public UserStatus after { get; set; } = default!; +} diff --git a/Luski.net/JsonTypes/WSS/IncomingWSS.cs b/Luski.net/JsonTypes/WSS/IncomingWSS.cs new file mode 100755 index 0000000..36501d0 --- /dev/null +++ b/Luski.net/JsonTypes/WSS/IncomingWSS.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.JsonTypes.BaseTypes; + +public class BetterIncomingWSS +{ + [JsonPropertyName("type")] + [JsonInclude] + public DataType? Type { get; set; } = DataType.Token!; + [JsonPropertyName("error")] + [JsonInclude] + public string Error { get; set; } = default!; + [JsonPropertyName("data")] + [JsonInclude] + public object? Data { get; set; } +} + +[JsonSerializable(typeof(BetterIncomingWSS))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, + WriteIndented = false)] +public partial class BetterIncomingWSSContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/WSS/ServerEvent.cs b/Luski.net/JsonTypes/WSS/ServerEvent.cs new file mode 100644 index 0000000..33cfa5e --- /dev/null +++ b/Luski.net/JsonTypes/WSS/ServerEvent.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.JsonTypes.WSS; + +public class ServerEvent +{ + [JsonInclude] + [JsonPropertyName("type")] + public DataType Type { get; set; } + + [JsonInclude] + [JsonPropertyName("data")] + public object Data { get; set; } +} \ No newline at end of file diff --git a/Luski.net/JsonTypes/WSS/WSSKeyExchange.cs b/Luski.net/JsonTypes/WSS/WSSKeyExchange.cs new file mode 100755 index 0000000..fd18e13 --- /dev/null +++ b/Luski.net/JsonTypes/WSS/WSSKeyExchange.cs @@ -0,0 +1,31 @@ +using Luski.net.Enums; +using Luski.net.JsonTypes.BaseTypes; +using System.Text.Json.Serialization; + +namespace Luski.net.JsonTypes.WSS; + +internal class WSSKeyExchange : IncomingWSS +{ + [JsonPropertyName("type")] + [JsonInclude] + new public DataType? Type { get; set; } = DataType.Key_Exchange; + [JsonPropertyName("channel")] + [JsonInclude] + public long channel { get; set; } = default!; + [JsonPropertyName("key")] + [JsonInclude] + public string key { get; set; } = default!; + [JsonPropertyName("to")] + [JsonInclude] + public long? to { get; set; } = default!; +} + +[JsonSerializable(typeof(WSSKeyExchange))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class WSSKeyExchangeContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/WSS/WSSLogin.cs b/Luski.net/JsonTypes/WSS/WSSLogin.cs new file mode 100755 index 0000000..7f1c690 --- /dev/null +++ b/Luski.net/JsonTypes/WSS/WSSLogin.cs @@ -0,0 +1,29 @@ +using Luski.net.Enums; +using Luski.net.JsonTypes.BaseTypes; +using System.Text.Json.Serialization; +using Luski.net.Interfaces; + +namespace Luski.net.JsonTypes.WSS; + +internal class WSSLogin : IncomingWSS, IServerEvent +{ + [JsonPropertyName("token")] + [JsonInclude] + public string Token { get; set; } = default!; + [JsonPropertyName("session_token")] + [JsonInclude] + public string? SessionToken { get; set; } = default!; + [JsonPropertyName("type")] + [JsonInclude] + new public DataType? Type { get; set; } = DataType.Login; +} + +[JsonSerializable(typeof(WSSLogin))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class WSSLoginContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/JsonTypes/WSS/WSSOut.cs b/Luski.net/JsonTypes/WSS/WSSOut.cs new file mode 100644 index 0000000..581f28a --- /dev/null +++ b/Luski.net/JsonTypes/WSS/WSSOut.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace Luski.net.JsonTypes.WSS; + +public class WSSOut +{ + [JsonInclude] + [JsonPropertyName("data")] + public ServerEvent Data { get; set; } + [JsonInclude] + [JsonPropertyName("send_type")] + public int SendType { get; set; } = 1; +} + +[JsonSerializable(typeof(WSSOut))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class WSSOutContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/Luski.net/Luski.net.csproj b/Luski.net/Luski.net.csproj new file mode 100755 index 0000000..485e5cb --- /dev/null +++ b/Luski.net/Luski.net.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + disable + enable + true + Luski.net + JacobTech + JacobTech, LLC + A wrapper for the luski API + https://www.jacobtech.com/Luski/Documentation + https://github.com/JacobTech-com/Luski.net + True + 1.0.0 + 2.0.1-alpha18 + + + + + + + + + + + + + + diff --git a/Luski.net/Luski.net.csproj.user b/Luski.net/Luski.net.csproj.user new file mode 100755 index 0000000..c5a8f00 --- /dev/null +++ b/Luski.net/Luski.net.csproj.user @@ -0,0 +1,6 @@ + + + + <_LastSelectedProfileId>C:\Users\techn\source\repos\JacobTech-com\Luski.net\Luski.net\Luski.net\Properties\PublishProfiles\FolderProfile.pubxml + + \ No newline at end of file diff --git a/Luski.net/MainServer.Account.cs b/Luski.net/MainServer.Account.cs new file mode 100644 index 0000000..9d97239 --- /dev/null +++ b/Luski.net/MainServer.Account.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Security.Authentication; +using System.Text; +using System.Text.Json; +using System.Threading; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.net.JsonTypes; +using Luski.net.JsonTypes.WSS; +using Luski.net.Structures.Main; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using WebSocketSharp; + +namespace Luski.net; + +public partial class MainServer +{ + public void Login(string Username, string Password, CancellationToken CancellationToken) + { + Both(Username, Password, CancellationToken); + } + + public void CreateAccount(string Username, string Password, string Displayname, string PFP, CancellationToken CancellationToken) + { + Both(Username, Password, CancellationToken, Displayname, PFP); + } + + private void Both(string Username, string Password, CancellationToken CancellationToken, string? Displayname = null, string? pfp = null) + { + if (!EncryptionHandler.Generating) + { + EncryptionHandler.GenerateKeys(); + } + while (!EncryptionHandler.Generated) { } + + login = true; + LoginSTC json; + List> heads = new() + { + new("key", EncryptionHandler.MyPublicKey), + new("email", Convert.ToBase64String(Encryption.RSA.Encrypt(Username, EncryptionHandler.ServerPublicKey, EncoderType.UTF16))), + new("password", EncryptionHandler.RemotePasswordEncrypt(Encoding.Unicode.GetBytes(Password))) + }; + if (File.Exists("LastPassVer.txt") && int.TryParse(File.ReadAllText("LastPassVer.txt"), out int lpv) && lpv < EncryptionHandler.PasswordVersion && lpv >= 0) + { + heads.Add(new("old_password", EncryptionHandler.RemotePasswordEncrypt(Encoding.Unicode.GetBytes(Password), lpv))); + heads.Add(new("old_version", lpv.ToString())); + } + if (pfp is not null) + { + heads.Add(new("username", Displayname)); + json = SendServer( + "CreateAccount", + pfp, + LoginSTCContext.Default.LoginSTC, + CancellationToken, + heads.ToArray()).Result; + } + else + { + json = GetFromServer( + "Login", + LoginSTCContext.Default.LoginSTC, + CancellationToken, + heads.ToArray()).Result; + } + if (json.Error is not null) throw new Exception($"Luski appears to be down at the current moment: {json.ErrorMessage}"); + if (EncryptionHandler.OfflinePrivateKey is null || EncryptionHandler.OfflinePublicKey is null) throw new Exception("Something went wrong generating the offline keys"); + login = false; + if (json is not null && json.Error is null) + { + ServerOut = new WebSocket($"{(Secure ? "wss" : "ws" )}://{Domain}/WSS/{ApiVersion}"); + if (Secure) ServerOut.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12; + ServerOut.OnMessage += DataFromServer; + ServerOut.WaitTime = new TimeSpan(0, 0, 5); + ServerOut.OnError += ServerOut_OnError; + ServerOut.Connect(); + SendServerOld(new WSSLogin() { Token = json.Token! }, WSSLoginContext.Default.WSSLogin); + while (Token is null && Error is null) + { + Thread.Sleep(500); + } + if (Error is not null) + { + throw new Exception(Error); + } + + if (Token is null) throw new Exception("Server did not send a token"); + CanRequest = true; + long id = long.Parse(Encoding.UTF8.GetString(Convert.FromBase64String( + Token.Split('.')[0] + ))); + User = GetUser(id, MainSocketAppUserContext.Default.MainSocketAppUser, + CancellationToken) + .Result; + if (User is null || User.Error is not null) + { + string error = "User was null"; + if (User is not null && User.Error is not null) error = $"{User.Error}: {User.ErrorMessage}"; + throw new Exception($"Something went wrong getting your user infermation\n{error}"); + } + _ = UpdateStatus(UserStatus.Online, CancellationToken); + if (pfp is not null) + { + //post + } + else + { + + } + EncryptionHandler.Hash = EncryptionHandler.LocalPasswordEncrypt(Encoding.Unicode.GetBytes(Username.ToLower() + Password)); + OfflineDataBlobSTC offlinedata = GetFromServer("Keys/GetOfflineData", OfflineDataBlobSTCContext.Default.OfflineDataBlobSTC, CancellationToken).Result; + if (offlinedata is not null && offlinedata.Error is null && offlinedata.Data is not null && offlinedata.Data.Length > 0) + { + foreach (string keyex in offlinedata.Data) + { + KeyExchange? okd = JsonSerializer.Deserialize(keyex); + if (okd is not null && !string.IsNullOrEmpty(okd.key)) + { + Storage.SetResourceKey( + StorageDirectory.ChannelKeys, + okd.channel.ToString(), + EncryptionHandler.Hash, + Encoding.Unicode.GetString( + Encryption.RSA.Decrypt( + Convert.FromBase64String(okd.key), + Storage.GetResourceKeyRaw( + StorageDirectory.ServerKeys, + "pkey", + EncryptionHandler.Hash + ) + ) + ) + ); + } + } + } + System.IO.File.WriteAllText("LastPassVer.txt", EncryptionHandler.PasswordVersion.ToString()); + Storage.SetResourceKey(StorageDirectory.ServerKeys, "pkey", EncryptionHandler.Hash, EncryptionHandler.OfflinePrivateKey); + using HttpClient setkey = new(); + setkey.DefaultRequestHeaders.Add("token", Token); + _ = setkey.PostAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/Keys/SetOfflineKey", new StringContent(EncryptionHandler.OfflinePublicKey)).Result; + foreach (var ch in chans) + { + _ = ch.Members; + } + EncryptionHandler.OfflinePublicKey = null; + EncryptionHandler.OfflinePrivateKey = null; + } + else throw new Exception(json?.ErrorMessage); + } +} \ No newline at end of file diff --git a/Luski.net/MainServer.Events.cs b/Luski.net/MainServer.Events.cs new file mode 100644 index 0000000..011ebe1 --- /dev/null +++ b/Luski.net/MainServer.Events.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; +using Luski.net.Structures; +using Luski.net.Structures.Main; + +namespace Luski.net; + +public partial class MainServer +{ + public event Func? MessageReceived; + + public event Func? ReceivedFriendRequest; + + public event Func? FriendRequestResult; +} \ No newline at end of file diff --git a/Luski.net/MainServer.Incoming.cs b/Luski.net/MainServer.Incoming.cs new file mode 100644 index 0000000..457df1a --- /dev/null +++ b/Luski.net/MainServer.Incoming.cs @@ -0,0 +1,125 @@ +using System; +using System.Text; +using System.Text.Json; +using System.Threading; +using JacobTechEncryption; +using Luski.net.Enums; +using Luski.net.JsonTypes; +using Luski.net.JsonTypes.BaseTypes; +using Luski.net.JsonTypes.HTTP; +using Luski.net.JsonTypes.WSS; +using Luski.net.Structures; +using Luski.net.Structures.Main; +using WebSocketSharp; + +namespace Luski.net; + +public partial class MainServer +{ + private void DataFromServer(object? sender, MessageEventArgs e) + { + if (e.IsPing) return; + try + { + Console.WriteLine("From Server: {0}", e.Data); + IncomingWSS? data = JsonSerializer.Deserialize(e.Data, IncomingWSSContext.Default.IncomingWSS); + switch (data?.Type) + { + case DataType.Login: + Console.WriteLine("Pre auth"); + WSSLogin n = JsonSerializer.Deserialize(e.Data, WSSLoginContext.Default.WSSLogin)!; + Token = n.Token; + Console.WriteLine("Token: {0}",Token); + break; + case DataType.Error: + if (Token is null) + { + Error = data.Error; + } + else + { + Exception(new Exception(data.Error)); + } + break; + case DataType.Message_Create: + if (MessageReceived is not null) + { + MainSocketMessage? m = JsonSerializer.Deserialize(e.Data); + if (m is not null) + { + m.decrypt(EncryptionHandler.GetChannelKey(m.ChannelID), CancellationToken.None); + _ = MessageReceived.Invoke(m); + } + } + break; + case DataType.Status_Update: + StatusUpdate? SU = JsonSerializer.Deserialize(e.Data); + if (SU is not null) + { + MainSocketRemoteUser after = GetUser(SU.id, MainSocketRemoteUserContext.Default.MainSocketRemoteUser, CancellationToken.None).Result; + after.Status = SU.after; + MainSocketRemoteUser before = after.Clone(); + before.Status = SU.before; + StatusUpdate(before, after); + } + break; + case DataType.Friend_Request: + if (ReceivedFriendRequest is not null) + { + FriendRequest? request = + JsonSerializer.Deserialize(e.Data, FriendRequestContext.Default.FriendRequest); + if (request is not null) + _ = ReceivedFriendRequest.Invoke(GetUser(request.Id, + MainSocketRemoteUserContext.Default.MainSocketRemoteUser, + CancellationToken.None).Result); + } + break; + case DataType.Friend_Request_Result: + FriendRequestResult? FRR = JsonSerializer.Deserialize(e.Data); + if (FRR is not null && FRR.Channel is not null && FRR.Id is not null && + FRR.Result is not null) + { + MainSocketDMChannel chan = GetChannel((long)FRR.Channel, + MainSocketDMChannelContext.Default.MainSocketDMChannel, + CancellationToken.None).Result; + chans.Add(chan); + MainSocketRemoteUser from1 = GetUser((long)FRR.Id, + MainSocketRemoteUserContext.Default.MainSocketRemoteUser, + CancellationToken.None).Result; + //from1.Channel = chan; + if (FriendRequestResult is not null) _ = FriendRequestResult.Invoke(from1, (bool)FRR.Result); + } + break; + case DataType.Key_Exchange: + try + { + KeyExchange? KE = JsonSerializer.Deserialize(e.Data); + if (KE is not null) + { + EncryptionHandler.SetChannelKey( + KE.channel, + Encoding.UTF8.GetString( + Encryption.RSA.Decrypt( + Convert.FromBase64String(KE.key), + EncryptionHandler.myPrivateKey) + ) + ); + } + } + catch (Exception ex) + { + Exception(ex); + } + + break; + default: + Console.WriteLine("Unknown"); + break; + } + } + catch (Exception exception) + { + Exception(exception); + } + } +} \ No newline at end of file diff --git a/Luski.net/MainServer.cs b/Luski.net/MainServer.cs new file mode 100644 index 0000000..f8c3c47 --- /dev/null +++ b/Luski.net/MainServer.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using Luski.net.Enums.Main; +using Luski.net.Interfaces; +using Luski.net.JsonTypes; +using Luski.net.JsonTypes.HTTP; +using Luski.net.Structures.Main; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using ChannelType = Luski.net.Enums.Main.ChannelType; + +namespace Luski.net; + +public partial class MainServer : Server +{ + internal MainServer(string Domain, string API_Version): + base(Domain, API_Version) + { + } + + public MainSocketAppUser User { get; internal set; } = default!; + + public List chans { get; } = new(); + + public async Task SendFriendResult(long user, bool answer, CancellationToken CancellationToken) + { + FriendRequestResult json = await SendServer("FriendRequestResult", + new FriendRequestResultOut() + { + Id = user, + Result = answer + }, + FriendRequestResultOutContext.Default.FriendRequestResultOut, + FriendRequestResultContext.Default.FriendRequestResult, + CancellationToken); + + if (json is not null && json.Error is null && json.ErrorMessage is null && answer && json.Channel is not null) + { + MainSocketDMChannel chan = await GetChannel((long)json.Channel, MainSocketDMChannelContext.Default.MainSocketDMChannel, CancellationToken); + _ = chan.StartKeyProcessAsync(CancellationToken); + chans.Add(chan); + } + else + { + throw new Exception(json?.Error.ToString()); + } + return GetUser(user, MainSocketRemoteUserContext.Default.MainSocketRemoteUser, CancellationToken).Result; + } + + public async Task SendFriendRequest(long code, CancellationToken CancellationToken) + { + string ccode = Convert.ToBase64String(Encryption.Hashing.SHA256(Encoding.Unicode.GetBytes(code.ToString()))); + FriendRequestResult? json = await SendServer("FriendRequest", new FriendRequest() { code = ccode}, FriendRequestContext.Default.FriendRequest, FriendRequestResultContext.Default.FriendRequestResult, CancellationToken); + + if (json.StatusCode != HttpStatusCode.Accepted) + { + if (json is not null && json.Error is not null) + { + switch ((ErrorCode)(int)json.Error) + { + case ErrorCode.InvalidToken: + throw new Exception("Your current token is no longer valid"); + case ErrorCode.ServerError: + throw new Exception($"Error from server: {json.ErrorMessage}"); + case ErrorCode.InvalidPostData: + throw new Exception("The post data dent to the server is not the correct format. This may be because you app is couropt or you are using the wron API version"); + case ErrorCode.Forbidden: + throw new Exception("You already have an outgoing request or the persone is not real"); + } + } + + if (json is not null && json.Channel is not null) + { + MainSocketDMChannel chan = await GetChannel((long)json.Channel, MainSocketDMChannelContext.Default.MainSocketDMChannel, CancellationToken); + _ = chan.StartKeyProcessAsync(CancellationToken); + chans.Add(chan); + } + } + + MainSocketRemoteUser b = await GetUser(code, MainSocketRemoteUserContext.Default.MainSocketRemoteUser, CancellationToken); + if (json.Channel is not null) + b.FriendStatus = FriendStatus.Friends; + else + b.FriendStatus = FriendStatus.PendingOut; + return b; + } + + public async Task GetChannel(long Channel, CancellationToken CancellationToken) where TChannel : MainSocketChannel, new() + { + TChannel Return = new(); + switch (Return) + { + case MainSocketDMChannel: + Return = (await GetChannel(Channel, MainSocketDMChannelContext.Default.MainSocketDMChannel, CancellationToken) as TChannel)!; + break; + case MainSocketGroupChannel: + Return = (await GetChannel(Channel, MainSocketGroupChannelContext.Default.MainSocketGroupChannel, CancellationToken) as TChannel)!; + break; + case MainSocketTextChannel: + Return = (await GetChannel(Channel, MainSocketTextChannelContext.Default.MainSocketTextChannel, CancellationToken) as TChannel)!; + break; + case MainSocketChannel: + Return = (await GetChannel(Channel, MainSocketChannelContext.Default.MainSocketChannel, CancellationToken) as TChannel)!; + break; + case null: + throw new NullReferenceException(nameof(TChannel)); + default: + throw new Exception("Unknown channel type"); + } + return Return; + } + + internal async Task GetChannel(long id, JsonTypeInfo Json, CancellationToken CancellationToken) where TChannel : MainSocketChannel, new() + { + TChannel request; + if (chans.Count > 0 && chans.Any(s => s.Id == id)) + { + return (chans.Where(s => s is TChannel && s.Id == id).First() as TChannel)!; + } + while (true) + { + if (CanRequest) + { + request = await GetFromServer($"SocketChannel/Get/{id}", Json, CancellationToken); + break; + } + } + if (request is null) throw new Exception("Something was wrong with the server responce"); + if (request.Error is null) + { + if (chans.Count > 0 && chans.Any(s => s.Id == request.Id)) + { + foreach (MainSocketChannel? p in chans.Where(s => s.Id == request.Id)) + { + chans.Remove(p); + } + } + request.Server = this; + chans.Add(request); + return request; + } + throw request.Error switch + { + ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"), + ErrorCode.Forbidden => new Exception("The server rejected your request"), + ErrorCode.ServerError => new Exception("Error from server: " + request.ErrorMessage), + ErrorCode.InvalidURL or ErrorCode.MissingHeader => new Exception(request.ErrorMessage), + _ => new Exception($"Unknown data: '{request.ErrorMessage}'"), + }; + } + + public Task GetUser(long UserID, CancellationToken CancellationToken) where Tuser : MainSocketUserBase, new() + { + Tuser user = new(); + switch (user) + { + case MainSocketAppUser: + user = (GetUser(UserID, MainSocketAppUserContext.Default.MainSocketAppUser, CancellationToken).Result as Tuser)!; + break; + case MainSocketRemoteUser: + user = (GetUser(UserID, MainSocketRemoteUserContext.Default.MainSocketRemoteUser, CancellationToken).Result as Tuser)!; + break; + case MainSocketUserBase: + user = (GetUser(UserID, MainSocketUserBaseContext.Default.MainSocketUserBase, CancellationToken).Result as Tuser)!; + break; + case null: + throw new NullReferenceException(nameof(Tuser)); + default: + throw new Exception("Unknown channel type"); + } + + return Task.FromResult(user); + } + + public async Task GetMessage(long id, CancellationToken CancellationToken) + { + MainSocketMessage message; + while (true) + { + if (CanRequest) + { + message = await GetFromServer("socketmessage", + MainSocketMessageContext.Default.MainSocketMessage, + CancellationToken, + new KeyValuePair("msg_id", id.ToString())); + break; + } + } + + if (message is not null) + { + message.Server = this; + return message; + } + throw new Exception("Server did not return a message"); + } + + /// + /// Sends the server a request to update the of you account + /// + /// The you want to set your status to + /// + public async Task UpdateStatus(UserStatus Status, CancellationToken CancellationToken) + { + STC? data = await SendServer("SocketUserProfile/Status", new StatusUpdateCTS() { Status = Status }, StatusUpdateCTSContext.Default.StatusUpdateCTS, STCContext.Default.STC, CancellationToken); + if (data.Error is not null && ((int)data.StatusCode < 200 || (int)data.StatusCode > 299)) + { + if (data?.ErrorMessage is not null) throw new Exception(data.ErrorMessage); + if (data?.Error is not null) throw new Exception(((int)data.Error).ToString()); + else throw new Exception("Something went worng"); + } + + User.Status = Status; + return Task.CompletedTask; + } + + internal async Task GetUser(long UserId, JsonTypeInfo Json, CancellationToken CancellationToken) where Tuser : MainSocketUserBase, new() + { + Tuser user; + if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId)) + { + Tuser temp = poeople.Where(s => s is Tuser && s.Id == UserId).Cast().FirstOrDefault()!; + if (temp is MainSocketRemoteUser && (temp as MainSocketRemoteUser)!.Channel == null) + { + foreach (MainSocketDMChannel chan in chans.Where(s => s is MainSocketDMChannel).Cast()) + { + if (chan.Type == ChannelType.DM && chan.Id != 0 && chan.MemberIdList is not null) + { + if (chan.MemberIdList.Any(s => s == UserId)) (temp as MainSocketRemoteUser)!.Channel = chan; + } + } + } + return temp; + } + while (true) + { + if (CanRequest) + { + user = await GetFromServer("socketuser", + Json, + CancellationToken, + new KeyValuePair("id", UserId.ToString())); + break; + } + } + + if (user is null) throw new Exception("Server did not return a user"); + if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId)) + { + foreach (IUser? p in poeople.Where(s => s.Id == UserId)) + { + poeople.Remove(p); + } + } + + user.Server = this; + if (user is MainSocketRemoteUser && (user as MainSocketRemoteUser)!.Channel == null) + { + foreach (MainSocketDMChannel chan in chans.Where(s => s is MainSocketDMChannel).Cast()) + { + if (chan.Type == ChannelType.DM && chan.Id != 0 && chan.MemberIdList is not null) + { + if (chan.MemberIdList.Any(s => s == UserId)) (user as MainSocketRemoteUser)!.Channel = chan; + } + } + } + poeople.Add(user); + return user; + } +} \ No newline at end of file diff --git a/Luski.net/Properties/PublishProfiles/FolderProfile.pubxml b/Luski.net/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100755 index 0000000..154e035 --- /dev/null +++ b/Luski.net/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + Release + Any CPU + bin\Release\net6.0\publish\ + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/Luski.net/Properties/PublishProfiles/FolderProfile.pubxml.user b/Luski.net/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100755 index 0000000..d26c1f6 --- /dev/null +++ b/Luski.net/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,10 @@ + + + + + True|2022-11-23T16:09:01.7347068Z;True|2022-11-23T11:07:47.9880607-05:00;True|2022-11-23T11:07:08.8325322-05:00;True|2022-11-23T11:05:40.5859900-05:00;True|2022-09-21T18:57:48.1433890-04:00;False|2022-09-21T18:56:37.2624157-04:00;True|2022-07-05T22:55:54.9271108-04:00; + + + \ No newline at end of file diff --git a/Luski.net/PublicServer.Account.cs b/Luski.net/PublicServer.Account.cs new file mode 100644 index 0000000..7af40df --- /dev/null +++ b/Luski.net/PublicServer.Account.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Authentication; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.net.JsonTypes; +using Luski.net.JsonTypes.WSS; +using Luski.net.Structures.Public; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using WebSocketSharp; +using DataType = Luski.Shared.PublicServers.V1.Enums.DataType; + +namespace Luski.net; + +public partial class PublicServer +{ + public async Task Login(string Username, string Password, CancellationToken CancellationToken) + { + return await All(Username, CancellationToken, Password); + } + + public async Task CreateAccount(string Username, string Password, string DisplayName, string PFP, CancellationToken CancellationToken) + { + return await All(Username, CancellationToken, Password, DisplayName, PFP); + } + + internal async Task LoginViaToken(string t) + { + Console.WriteLine("Starting Auto Login:"); + bool b = await All(t, CancellationToken.None); + Console.WriteLine($"Auto Login {(b ? "Successful" : "Failed")}"); + return b; + } + + private async Task All(string Username, CancellationToken CancellationToken, string? Password = null, string? Displayname = null, string? pfp = null) + { + DateTime dt = DateTime.UtcNow; + Console.WriteLine("Encryption: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + if (!EncryptionHandler.Generating) + { + EncryptionHandler.GenerateKeys(); + } + while (!EncryptionHandler.Generated) { Thread.Sleep(200); } + Console.WriteLine("Encryption 2: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + List> FailedSystems = new(); + login = true; + LoginSTC json; + List> heads = new() + { + new("key", EncryptionHandler.MyPublicKey), + new((Password is null ? "token" : "username"), Convert.ToBase64String(Encryption.RSA.Encrypt(Username, EncryptionHandler.ServerPublicKey, EncoderType.UTF16))) + }; + if (Password is not null) + { + heads.Add(new("password", EncryptionHandler.RemotePasswordEncrypt(Encryption.Generic.Encoders[(int)EncoderType.UTF16].GetBytes(Password)))); + } + if (File.Exists("LastPassVer.txt") && int.TryParse(File.ReadAllText("LastPassVer.txt"), out int lpv) && lpv < EncryptionHandler.PasswordVersion && lpv >= 0) + { + heads.Add(new("old_password", EncryptionHandler.RemotePasswordEncrypt(Encoding.Unicode.GetBytes(Password), lpv))); + heads.Add(new("old_version", lpv.ToString())); + } + if (pfp is not null) + { + heads.Add(new("displayname", Displayname)); + json = await SendServer( + "SocketAccount", + pfp, + LoginSTCContext.Default.LoginSTC, + CancellationToken, + heads.ToArray()); + } + else + { + json = await GetFromServer( + (Password is null ? "SocketAccount/AccessToken": "SocketAccount"), + LoginSTCContext.Default.LoginSTC, + CancellationToken, + heads.ToArray()); + } + Console.WriteLine("Account Result: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + if (json.Error is not null) + { + ConnectionStatus = ConnectionStatus.FailedToLogin; + Console.WriteLine(json.ErrorMessage); + return false; + throw new Exception($"Luski appears to be down at the current moment: {json.ErrorMessage}"); + } + if (EncryptionHandler.OfflinePrivateKey is null || EncryptionHandler.OfflinePublicKey is null) throw new Exception("Something went wrong generating the offline keys"); + login = false; + if (json is not null && json.Error is null) + { + ServerOut = new WebSocket(wssurl); + ServerOut.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12; + ServerOut.OnMessage += DataFromServer; + ServerOut.WaitTime = new TimeSpan(0, 0, 5); + ServerOut.EmitOnPing = true; + ServerOut.OnError += ServerOut_OnError; + ServerOut.Connect(); + Console.WriteLine("WSS Connection: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + SendServer(DataType.Token, new WSSLogin() { Token = json.Token! }); + while (Token is null && Error is null) + { + Thread.Sleep(200); + } + if (Error is not null) + { + throw new Exception(Error); + } + Console.WriteLine("WSS Login: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + + if (Token is null) throw new Exception("Server did not send a token"); + CanRequest = true; + long id = long.Parse(Encoding.UTF8.GetString(Convert.FromBase64String( + Token.Split('.')[0] + ))); + User = await GetUser(id, CancellationToken); + ConnectionStatus = ConnectionStatus.LoggedIn; + User.Username = Username; + Console.WriteLine("Get our info: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + + #region Extra Systems + + Task.Run(async () => { + #region Data Storage + try + { + StorageInfoSTC data; + if (Storage.StorageID == 0) + { + EncryptionHandler.Hash = Storage.GenerateStorage(); + data = await SendServer("OfflineData/Info", + new StorageInfoCTS() + { + Password = Convert.ToBase64String(Encryption.AES.Encrypt(EncryptionHandler.Hash, Storage.GetResourceBytes(StorageDirectory.StorageInfo, "lpk"))) + }, + StorageInfoCTSContext.Default.StorageInfoCTS, + StorageInfoSTCContext.Default.StorageInfoSTC, CancellationToken, + new KeyValuePair("storage_id", Storage.StorageID.ToString())); + Storage.setid(data.ID); + } + else + { + data = await GetFromServer("OfflineData/Info", StorageInfoSTCContext.Default.StorageInfoSTC, CancellationToken, new KeyValuePair("storage_id", Storage.StorageID.ToString())); + } + Console.WriteLine("Offline Data Info: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + if (data.Update) + { + EncryptionHandler.Hash = Storage.UpdateStorage(Convert.FromBase64String(data.Password)); + _ = await SendServerPatch("OfflineData/Info", + new StorageInfoCTS() + { + Password = Convert.ToBase64String(Encryption.AES.Encrypt(EncryptionHandler.Hash, Storage.GetResourceBytes(StorageDirectory.StorageInfo, "lpk"))) + }, + StorageInfoCTSContext.Default.StorageInfoCTS, + StorageInfoSTCContext.Default.StorageInfoSTC, CancellationToken, + new KeyValuePair("storage_id", Storage.StorageID.ToString())); + Console.WriteLine("Data Update: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + } + } + catch (Exception e) + { + FailedSystems.Add(new("Data Storage", e.Message, e)); + } + #endregion + + #region Key Generation + try + { + try + { + _ = EncryptionHandler.GetKey(0); + } + catch (Exception e) + { + EncryptionHandler.SetKey(0, new() + { + EncryptionType = EncryptionType.None, + Key = string.Empty + }); + Console.WriteLine("Key 0: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + } + } + catch (Exception e) + { + FailedSystems.Add(new("Key Generation", "Key 0 Failed to generate", e)); + } + #endregion + + #region Auto Status + try + { + _ = await UpdateStatus(UserStatus.Online, CancellationToken); + Console.WriteLine("Status: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + } + catch (Exception e) + { + FailedSystems.Add(new("Auto Status", "Failed to set status on the server", e)); + } + #endregion + + #region GetRoles + try + { + _ = await GetRoles(); + } + catch (Exception e) + { + FailedSystems.Add(new("Get Roles", "Failed to get roles on the server", e)); + } + #endregion + + #region Local Storage Cleanup + try + { + KeyValuePair stor = new("storage_id", Storage.StorageID.ToString()); + OfflineDataBlobSTC offlinedata = await GetFromServer("OfflineData", OfflineDataBlobSTCContext.Default.OfflineDataBlobSTC, CancellationToken, stor); + Console.WriteLine("Offline Data: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + if (offlinedata is not null && offlinedata.Error is null && offlinedata.Data is not null && offlinedata.Data.Length > 0) + { + string pkey = Storage.GetResourceKeyRaw( + StorageDirectory.ServerKeys, + "pkey", + EncryptionHandler.Hash + ); + foreach (string keyexx in offlinedata.Data) + { + string keyex = Encoding.UTF8.GetString(Convert.FromBase64String(keyexx)); + KeyExchange? okd = JsonSerializer.Deserialize(keyex); + if (okd is not null && !string.IsNullOrEmpty(okd.key)) + { + Storage.SetResourceKey( + StorageDirectory.ServerKeys, + okd.id.ToString(), + EncryptionHandler.Hash, + Encoding.Unicode.GetString( + Encryption.RSA.Decrypt( + Convert.FromBase64String(okd.key), + pkey + ) + ) + ); + } + } + } + System.IO.File.WriteAllText("LastPassVer.txt", EncryptionHandler.PasswordVersion.ToString()); + Storage.SetResourceKey(StorageDirectory.ServerKeys, "pkey", EncryptionHandler.Hash, EncryptionHandler.OfflinePrivateKey); + KeyPostCTS OfflineKeySetRequest = new() + { + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes(EncryptionHandler.OfflinePublicKey)), + EncryptionType = EncryptionType.RSA + }; + _ = await SendServer("Keys/SetOfflineKey", + OfflineKeySetRequest, + KeyPostCTSContext.Default.KeyPostCTS, + STCContext.Default.STC, + CancellationToken.None, + stor); + Console.WriteLine("Offline Key: " + DateTime.UtcNow.Subtract(dt).ToString("g")); + EncryptionHandler.OfflinePublicKey = null!; + EncryptionHandler.OfflinePrivateKey = null!; + } + catch (Exception e) + { + FailedSystems.Add(new("Local Storage Cleanup", "Failed to clean the local storage", e)); + } + #endregion + + if (FailedSystems.Count > 0) + { + Console.WriteLine("Some systems have failed:"); + foreach (Tuple System in FailedSystems) + { + Console.WriteLine($"\t{System.Item1}:\n\t\tMessage: {System.Item2}\n\t\tError Details: {System.Item3}"); + } + } }); + + + #endregion + + return true; + } + else throw new Exception(json?.ErrorMessage); + + return true; + } +} \ No newline at end of file diff --git a/Luski.net/PublicServer.Incoming.cs b/Luski.net/PublicServer.Incoming.cs new file mode 100644 index 0000000..d2ba892 --- /dev/null +++ b/Luski.net/PublicServer.Incoming.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.net.JsonTypes.BaseTypes; +using Luski.net.JsonTypes.WSS; +using Luski.net.Structures; +using Luski.net.Structures.Public; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using Luski.Shared.PublicServers.V1.Shared; +using WebSocketSharp; +using File = System.IO.File; +using DataType = Luski.Shared.PublicServers.V1.Enums.DataType; + +namespace Luski.net; + +public partial class PublicServer +{ + private void DataFromServer(object? sender, MessageEventArgs e) + { + if (e.IsPing) return; + if (PrintServerMessages) + { + Console.WriteLine(e.Data); + } + try + { + BetterIncomingWSS? data = JsonSerializer.Deserialize(e.Data, BetterIncomingWSSContext.Default.BetterIncomingWSS); + switch (data?.Type) + { + case DataType.Token: + WSSLogin n = JsonSerializer.Deserialize(e.Data, WSSLoginContext.Default.WSSLogin)!; + File.WriteAllText(Storage.GetStorageDirectory(StorageDirectory.StorageInfo) + "token", + n.SessionToken); + Token = n.Token; + break; + case DataType.StatusUpdate: + StatusEvent se = JsonSerializer.Deserialize(data.Data!.ToString()!, StatusEventContext.Default.StatusEvent)!; + SocketUser u; + if (se.User == User.Id) u = User; + else + { + u = GetUser(se.User); + u.Status = se.After; + } + if (StatusUpdate is not null) + { + StatusUpdate.Invoke(se.Before, u); + } + break; + case DataType.Role: + RoleEvent re = JsonSerializer.Deserialize(data.Data!.ToString()!, RoleEventContext.Default.RoleEvent)!; + Role[] ra = roles.Where(s => s.ID == re.ID).ToArray(); + Role r; + bool @new = false; + if (ra.Length > 0) + { + r = ra[0]; + if (re.ServerPermissions is not null) r.ServerPermissions = re.ServerPermissions.Value; + if (re.Description is not null) r.Description = re.Description; + if (re.DisplayName is not null) r.DisplayName = re.DisplayName; + if (re.ColorType is not null) r.ColorType = re.ColorType.Value; + if (re.Color is not null) + { + if (r.ColorType == ColorType.Full) + { + Color nc = new(re.Color); + r.Colors = new []{nc}; + } + else + { + List cols = new(); + for (int i = 0; i < re.Color.Length - 7; i+=8) + { + cols.Add(new(re.Color[i..(i+8)])); + } + r.Colors = cols.ToArray(); + } + } + if (re.Index is not null) r.Index = re.Index.Value; + } + else + { + @new = true; + Task rr = GetRole(re.ID); + rr.Wait(); + r = rr.Result; + } + + if (RoleEvent is not null) _ = RoleEvent.Invoke(@new, r); + break; + case DataType.RoleMember: + RoleMemberEvent rme = JsonSerializer.Deserialize(data.Data!.ToString()!, RoleMemberEventContext.Default.RoleMemberEvent)!; + break; + case DataType.RoleDelete: + break; + case DataType.MessageCreate: + MessageSTC smsg = JsonSerializer.Deserialize(data.Data!.ToString()!, MessageSTCContext.Default.MessageSTC)!; + List fl = new(); + List sf = new(); + foreach (ServerFileInfoSTC v in smsg.Files) + { + if (v.NameKey == 0) + { + if (string.IsNullOrEmpty(v.Name)) + { + v.Name = ""; + } + else v.Name = Encryption.Generic.Encoders[(int)v.NameEncoder] + .GetString(Convert.FromBase64String(v.Name)); + } + else + { + LocalKeyInfo key = EncryptionHandler.GetKey(v.NameKey); + switch (key.EncryptionType) + { + case EncryptionType.RSA: + v.Name = Encryption.RSA.Decrypt(Convert.FromBase64String(v.Name), key.Key, + v.NameEncoder); + break; + default: + v.Name = Encryption.Generic.Encoders[(int)v.NameEncoder] + .GetString(Convert.FromBase64String(v.Name)); + break; + } + } + fl.Add(v.ID); + sf.Add(new() + { + ID = v.ID, + Size = v.Size, + Name = v.Name, + Encoder = v.Encoder, + NameEncoder = v.NameEncoder, + Key = v.Key, + NameKey = v.NameKey, + Server = this + }); + } + + if (smsg.EncryptionKey == 0) + { + if (string.IsNullOrEmpty(smsg.Context)) + { + smsg.Context = ""; + } + else smsg.Context = Encryption.Generic.Encoders[(int)smsg.EncoderType] + .GetString(Convert.FromBase64String(smsg.Context)); + } + else + { + LocalKeyInfo key = EncryptionHandler.GetKey(smsg.EncryptionKey); + switch (key.EncryptionType) + { + case EncryptionType.RSA: + smsg.Context = Encryption.RSA.Decrypt(Convert.FromBase64String(smsg.Context), key.Key, + smsg.EncoderType); + break; + default: + smsg.Context = Encryption.Generic.Encoders[(int)smsg.EncoderType] + .GetString(Convert.FromBase64String(smsg.Context)); + break; + } + } + SocketMessage sm = new() + { + ID = smsg.ID, + AuthorID = smsg.AuthorID, + TimeStamp = smsg.Timestamp, + ChannelID = smsg.ChannelID, + Context = smsg.Context, + EncoderType = smsg.EncoderType, + EncryptionKey = smsg.EncryptionKey, + FileIDs = fl.ToArray(), + Server = this, + ProfileID = smsg.ProfileID, + _Files = sf + }; + if (MessageReceived is not null) MessageReceived.Invoke(sm); + break; + default: + Console.WriteLine("Unknown"); + break; + } + } + catch (Exception exception) + { + Exception(exception); + } + } +} \ No newline at end of file diff --git a/Luski.net/PublicServer.cs b/Luski.net/PublicServer.cs new file mode 100644 index 0000000..436fabd --- /dev/null +++ b/Luski.net/PublicServer.cs @@ -0,0 +1,834 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes; +using Luski.net.JsonTypes.HTTP; +using Luski.net.Structures; +using Luski.net.Structures.Public; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using Luski.Shared.PublicServers.V1.Shared; +using Role = Luski.net.Structures.Public.Role; +using SocketUser = Luski.net.Structures.Public.SocketUser; + +namespace Luski.net; + +public partial class PublicServer : Server +{ + public event Func? MessageReceived; + public event Func? StatusUpdate; + public event Func? RoleEvent; + public event Func? RoleDeleted; + public event Func? RoleMemberEvent; + public PictureType PictureType { get; private set; } + public List chans { get; } = new(); + public List cats { get; } = new(); + public List roles { get; } = new(); + public List roleso { get; } = new(); + + public SocketAppUser User { get; private set; } = null!; + + private PublicServer(string Domain, string API_Version, bool Secure = true) : + base(Domain, API_Version, Secure) + { } + + internal static async Task GetServer(List Failed, string Domain, string API_Version, bool Secure = true, bool GenerateEncryption = true, bool LogConsole = false) + { + DateTime dt = DateTime.UtcNow; + Console.WriteLine("Connecting to public server '{0}' using API {1}.", Domain, API_Version); + PublicServer s = new(Domain, API_Version, Secure); + s.PrintServerMessages = LogConsole; + if (GenerateEncryption) + { + Thread t = new(_ => + { + s.EncryptionHandler.GenerateKeys(); + }); + t.Start(); + } + ServerInfoSTC? si = null; + try + { + si = await s.GetFromServer("socketserver", ServerInfoSTCContext.Default.ServerInfoSTC, CancellationToken.None); + s.EncryptionHandler.ServerPublicKey = await (await new HttpClient() + .GetAsync($"{(s.Secure ? "https" : "http")}://{s.Domain}/{s.ApiVersion}/Keys/PublicKey")) + .Content + .ReadAsStringAsync(); + } + catch (Exception e) + { + LocalServerInfo ServerListing = s.Storage.GetJson(StorageDirectory.ServerInfo, "server.json", true, + LocalServerInfoContext.Default.LocalServerInfo); + s.Name = ServerListing.Name; + s.Description = ServerListing.Description; + s.PictureType = ServerListing.PictureType; + if (ServerListing.AlternateServers.Length > 0) + { + Console.WriteLine("Failed to connect to public server '{0}' using API {1}. Attempting to connect to alternate servers.", Domain, API_Version); + foreach (ServerData Server in ServerListing.AlternateServers) + { + s.Secure = Server.Secure; + s.Domain = Server.DomainAndPort; + + try + { + + si = await s.GetFromServer("socketserver", ServerInfoSTCContext.Default.ServerInfoSTC, CancellationToken.None); + s.EncryptionHandler.ServerPublicKey = await (await new HttpClient() + .GetAsync($"{(s.Secure ? "https" : "http")}://{s.Domain}/{s.ApiVersion}/Keys/PublicKey")) + .Content + .ReadAsStringAsync(); + Console.WriteLine("Public server '{0}' connection restored by alternate server '{1}' using API {2}.", Domain, s.Domain, API_Version); + break; + } + catch + { + // ignored + } + } + } + + if (si is null) + { + Failed.Add(s); + s.ConnectionStatus = ConnectionStatus.FailedToConnect; + throw; + } + } + + s.ConnectionStatus = ConnectionStatus.ConnectedNotLoggedIn; + s.Storage.SetJson(StorageDirectory.ServerInfo, "server.json", new() + { + AlternateServers = Array.Empty(), + PictureType = si.PictureType, + Name = si.Name, + Description = si.Description + }, + LocalServerInfoContext.Default.LocalServerInfo); + s.Name = si.Name; + s.PictureType = si.PictureType; + s.Description = si.Description; + s.wssurl = si.WSSv4Address; + s.ServerType = ServerType.Public; + s.OwnerID = si.Owner; + Console.WriteLine("Connected to public server '{0}' using API {1} in {4}.\nServer Name: {2}\nServer Description: {3}", Domain, API_Version, s.Name, s.Description, DateTime.UtcNow.Subtract(dt).ToString("g")); + return s; + } + + public long OwnerID { get; private set; } = 0; + + public async Task GetCategory(long id, CancellationToken CancellationToken) where TCategory : SocketCategory, new() + { + CategorySTC request; + if (cats.Count > 0 && cats.Any(s => s.ID == id)) + { + return (cats.Where(s => s is TCategory && s.ID == id).First() as TCategory)!; + } + while (true) + { + if (CanRequest) + { + request = await GetFromServer($"SocketCategory", CategorySTCContext.Default.CategorySTC, CancellationToken, new KeyValuePair("id", id.ToString())); + break; + } + } + if (request is null) throw new Exception("Something was wrong with the server responce"); + if (request.Error is null) + { + if (cats.Count > 0 && cats.Any(s => s.ID == request.ID)) + { + foreach (SocketCategory? p in cats.Where(s => s.ID == request.ID)) + { + cats.Remove(p); + } + } + + LocalKeyInfo deckey; + if (request.DescriptionEncryptionKey != 0) + deckey = EncryptionHandler.GetKey(request.DescriptionEncryptionKey); + else deckey = new() + { + EncryptionType = EncryptionType.None, + Key = string.Empty + }; + string dec = deckey.EncryptionType switch + { + EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Description), deckey.Key, request.DescriptionEncoderType), + EncryptionType.AES => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString( + Encryption.AES.Decrypt(Convert.FromBase64String(request.Description), deckey.Key)), + _ => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString(Convert.FromBase64String(request.Description)) + }; + LocalKeyInfo nkey; + if (request.TitleEncryptionKey != 0) + nkey = EncryptionHandler.GetKey(request.TitleEncryptionKey); + else nkey = new() + { + EncryptionType = EncryptionType.None, + Key = string.Empty + }; + string n = nkey.EncryptionType switch + { + EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Name), nkey.Key, request.TitleEncoderType), + EncryptionType.AES => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString( + Encryption.AES.Decrypt(Convert.FromBase64String(request.Name), nkey.Key)), + _ => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString(Convert.FromBase64String(request.Name)) + }; + + TCategory bob = new() + { + ID = request.ID, + ParentID = request.Parent, + Description = dec, + DescriptionEncoderType = request.DescriptionEncoderType, + DescriptionEncryptionKey = request.DescriptionEncryptionKey, + Channels = request.Channels, + Categories = request.InnerCategories, + Name = n, + RoleOverides = request.RoleOverrides, + UserOverides = request.UserOverrides, + TitleEncoderType = request.TitleEncoderType, + TitleEncryptionKey = request.TitleEncryptionKey, + Server = this + }; + cats.Add(bob); + return bob; + } + throw request.Error switch + { + ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"), + ErrorCode.Forbidden => new Exception("The server rejected your request"), + ErrorCode.ServerError => new Exception("Error from server: " + request.ErrorMessage), + ErrorCode.InvalidURL or ErrorCode.MissingHeader => new Exception(request.ErrorMessage), + _ => new Exception($"Unknown data: '{request.ErrorMessage}'"), + }; + } + + public async Task GetRole(long id) + { + Role[] r = roles.Where(s => s.ID == id).ToArray(); + if (r.Length > 0) return r[0]; + RoleSTC s = await GetFromServer("SocketRole?id=" + id.ToString(), RoleSTCContext.Default.RoleSTC, CancellationToken.None); + + List cols = new(); + if (s.ColorType == ColorType.Full) + { + Color nc = new(s.Color); + cols.Add(nc); + } + else + { + for (int i = 0; i < s.Color.Length - 7; i+=8) + { + cols.Add(new(s.Color[i..(i+8)])); + } + } + + Role role = new() + { + Server = this, + ID = s.ID, + ColorType = s.ColorType, + Colors = cols.ToArray(), + Description = s.Description, + DisplayName = s.DisplayName, + MembersListID = s.Members, + Index = s.Index, + ServerPermissions = s.ServerPermissions + }; + roles.Add(role); + return role; + } + + public async Task GetRoles() + { + RolesSTC s = await GetFromServer("SocketRole/GetAll", RolesSTCContext.Default.RolesSTC, CancellationToken.None); + roles.Clear(); + foreach (var ServerRole in s.Roles) + { + List cols = new(); + if (ServerRole.ColorType == ColorType.Full) + { + Color nc = new(ServerRole.Color); + cols.Add(nc); + } + else + { + for (int i = 0; i < ServerRole.Color.Length - 7; i+=8) + { + cols.Add(new(ServerRole.Color[i..(i+8)])); + } + } + + roles.Add(new Role() + { + Server = this, + ID = ServerRole.ID, + ColorType = ServerRole.ColorType, + Colors = cols.ToArray(), + Description = ServerRole.Description, + DisplayName = ServerRole.DisplayName, + MembersListID = ServerRole.Members, + Index = ServerRole.Index, + ServerPermissions = ServerRole.ServerPermissions + }); + } + + return roles.ToArray(); + } + + public async Task SendMessage(TChannel channel, string msg, SocketMessage? ReplyTo = null, + ServerProfile? FakeProfile = null, params string[] files) where TChannel : SocketChannel, new() + { + List lll = new(); + + foreach (var f in files) + { + lll.Add(await UploadFile(f)); + } + + return await SendMessage(channel, msg, ReplyTo, FakeProfile, lll.ToArray()); + } + + public async Task UploadFile(string File) + { + FileInfo FI = new FileInfo(File); + ServerFileInfoSTC res = await SendServer("SocketFile", File, ServerFileInfoSTCContext.Default.ServerFileInfoSTC, + CancellationToken.None, + new("name_encoder", "0"), + new("encoder", "0"), + new("name_encryption", "0"), + new("encryption", "0"), + new("name", Convert.ToBase64String(Encoding.UTF8.GetBytes(FI.Name)))); + if (res.ErrorMessage is not null || res.Error is not null) + { + Console.WriteLine("Error {0}: {1}", (res.Error is null ? "Unknown" : res.Error.Value), res.ErrorMessage); + } + return new SocketFile() + { + ID = res.ID, + Encoder = EncoderType.UTF8, + Key = 0, + Name = FI.Name, + NameEncoder = EncoderType.UTF8, + NameKey = 0, + Server = this, + Size = FI.Length + }; + } + + public async Task SendMessage(TChannel channel, string msg, SocketMessage? ReplyTo = null, + ServerProfile? FakeProfile = null, params SocketFile[] files) where TChannel : SocketChannel, new() + { + string bc = ""; + if (channel.EncryptionKeys[0] == 0) + { + if (!string.IsNullOrEmpty(msg)) + bc = Convert.ToBase64String(Encryption.Generic.Encoders[(int)channel.EncoderTypes[0]].GetBytes(msg)); + } + else + { + LocalKeyInfo key = channel.Server.EncryptionHandler.GetKey(channel.EncryptionKeys[0]); + bc = Convert.ToBase64String(key.EncryptionType switch + { + EncryptionType.RSA => Encryption.RSA.Encrypt(msg, key.Key, channel.EncoderTypes[0]), + _ => Encryption.AES.Encrypt(Encryption.Generic.Encoders[(int)channel.EncoderTypes[0]].GetBytes(msg), key.Key) + }); + } + + List lll = new(); + + foreach (var f in files) + { + lll.Add(f.ID); + } + + + MessageCTS pcsm = new() + { + Files = lll.ToArray(), + ChannelID = channel.ID, + EncryptionKey = channel.EncryptionKeys[0], + Encoding = channel.EncoderTypes[0], + Base64Context = bc, + }; + if (FakeProfile is not null) + { + pcsm.Profile = FakeProfile.ID; + } + + + + MessageSTC smsg = await channel.Server.SendServer("socketmessage", pcsm, + MessageCTSContext.Default.MessageCTS, MessageSTCContext.Default.MessageSTC, + CancellationToken.None); + List fl = new(); + foreach (var VARIABLE in smsg.Files) + { + fl.Add(VARIABLE.ID); + } + SocketMessage sm = new() + { + ID = smsg.ID, + AuthorID = smsg.AuthorID, + ProfileID = smsg.ProfileID, + ChannelID = channel.ID, + TimeStamp = smsg.Timestamp, + Context = msg, + EncoderType = smsg.EncoderType, + EncryptionKey = smsg.EncryptionKey, + FileIDs = fl.ToArray(), + Server = channel.Server, + _Files = files.ToList() + }; + return sm; + } + + public async Task CreateRole(RolePostCTS Req) + { + RoleSTC res = await SendServer("SocketRole", + Req, + RolePostCTSContext.Default.RolePostCTS, + RoleSTCContext.Default.RoleSTC, + CancellationToken.None); + List cols = new(); + if (res.ColorType == ColorType.Full) + { + Color nc = new(res.Color); + cols.Add(nc); + } + else + { + for (int i = 0; i < res.Color.Length - 7; i+=8) + { + cols.Add(new(res.Color[i..(i+8)])); + } + } + + Role role = new Role() + { + DisplayName = res.DisplayName, + ServerPermissions = res.ServerPermissions, + Description = res.Description, + Index = res.Index, + MembersListID = res.Members, + ColorType = res.ColorType, + Colors = cols.ToArray(), + Server = this, + ID = res.ID + }; + roles.Add(role); + return role; + } + + public async Task EditRole(Role role, string? Name = null, string? Description = null, int? Index = null, ServerPermission? Permissions = null, string? Color = null, ColorType? colorType = null) + { + RoleSTC res = await SendServerPatch("SocketRole", + new() + { + ID = role.ID, + Description = Description, + DisplayName = Name, + Index = Index, + ColorType = colorType, + Color = Color, + ServerPermissions = Permissions + }, + RolePatchCTSContext.Default.RolePatchCTS, + RoleSTCContext.Default.RoleSTC, + CancellationToken.None); + if (Permissions is not null) role.ServerPermissions = res.ServerPermissions; + if (Description is not null) role.Description = res.Description; + if (Index is not null) role.Index = res.Index; + if (Name is not null) role.DisplayName = res.DisplayName; + if (Color is not null) role.Colors = new []{new Color(res.Color)}; + if (colorType is not null) role.ColorType = res.ColorType; + return role; + } + + public async Task EditRoleMembers(Role r) + { + return r; + } + + public async Task GetChannel(long id, CancellationToken CancellationToken) where TChannel : SocketChannel, new() + { + ChannelSTC request; + if (chans.Count > 0 && chans.Any(s => s.ID == id)) + { + return (chans.Where(s => s is TChannel && s.ID == id).First() as TChannel)!; + } + while (true) + { + if (CanRequest) + { + request = await GetFromServer($"SocketChannel", ChannelSTCContext.Default.ChannelSTC, CancellationToken, new KeyValuePair("id", id.ToString())); + break; + } + } + if (request is null) throw new Exception("Something was wrong with the server responce"); + if (request.Error is null) + { + if (chans.Count > 0 && chans.Any(s => s.ID == request.ID)) + { + foreach (SocketChannel? p in chans.Where(s => s.ID == request.ID)) + { + chans.Remove(p); + } + } + + LocalKeyInfo deckey; + if (request.DescriptionEncryptionKey != 0) + deckey = EncryptionHandler.GetKey(request.DescriptionEncryptionKey); + else deckey = new() + { + EncryptionType = EncryptionType.None, + Key = string.Empty + }; + string dec = deckey.EncryptionType switch + { + EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Description), deckey.Key, request.DescriptionEncoderType), + EncryptionType.AES => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString( + Encryption.AES.Decrypt(Convert.FromBase64String(request.Description), deckey.Key)), + _ => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString(Convert.FromBase64String(request.Description)) + }; + LocalKeyInfo nkey; + if (request.TitleEncryptionKey != 0) + nkey = EncryptionHandler.GetKey(request.TitleEncryptionKey); + else nkey = new() + { + EncryptionType = EncryptionType.None, + Key = string.Empty + }; + string n = nkey.EncryptionType switch + { + EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Name), nkey.Key, request.TitleEncoderType), + EncryptionType.AES => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString( + Encryption.AES.Decrypt(Convert.FromBase64String(request.Name), nkey.Key)), + _ => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString(Convert.FromBase64String(request.Name)) + }; + + TChannel bob = new() + { + ID = request.ID, + CategoryID = request.Parent, + Description = dec, + DescriptionEncoderType = request.DescriptionEncoderType, + DescriptionEncryptionKey = request.DescriptionEncryptionKey, + EncoderTypes = request.EncoderTypes, + EncryptionKeys = request.EncryptionKeys, + Epoch = request.Epoch, + Name = n, + RoleOverrides = request.RoleOverrides, + UserOverrides = request.UserOverrides, + Type = request.Type, + TitleEncoderType = request.TitleEncoderType, + TitleEncryptionKey = request.TitleEncryptionKey, + PictureType = request.PictureType, + Server = this, + }; + + chans.Add(bob); + return bob; + } + throw request.Error switch + { + ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"), + ErrorCode.Forbidden => new Exception("The server rejected your request"), + ErrorCode.ServerError => new Exception("Error from server: " + request.ErrorMessage), + ErrorCode.InvalidURL or ErrorCode.MissingHeader => new Exception(request.ErrorMessage), + _ => new Exception($"Unknown data: '{request.ErrorMessage}'"), + }; + } + + public async Task MakeChannel(SocketCategory parent, string Name, string Decription) + { + ChannelSTC res = await SendServer( + "SocketChannel", + new ChannelPostCTS() + { + Name = Convert.ToBase64String(Encoding.UTF8.GetBytes(Name)), + Description = Convert.ToBase64String(Encoding.UTF8.GetBytes(Decription)), + EncoderTypes = new[] { EncoderType.UTF16 }, + EncryptionKeys = new long[] { 0 }, + DescriptionEncoderType = EncoderType.UTF8, + TitleEncoderType = EncoderType.UTF8, + Parent = parent.ID, + DescriptionEncryptionKey = 0, + TitleEncryptionKey = 0, + RoleOverrides = Array.Empty(), + UserOverrides = Array.Empty(), + Type = ChannelType.TextAndVoice, + PictureType = PictureType.none + }, + ChannelPostCTSContext.Default.ChannelPostCTS, + ChannelSTCContext.Default.ChannelSTC, + CancellationToken.None); + + return new SocketChannel() + { + ID = res.ID, + CategoryID = res.Parent, + Description = Decription, + DescriptionEncoderType = res.DescriptionEncoderType, + DescriptionEncryptionKey = res.DescriptionEncryptionKey, + EncoderTypes = res.EncoderTypes, + EncryptionKeys = res.EncryptionKeys, + Epoch = res.Epoch, + Name = Name, + RoleOverrides = res.RoleOverrides, + UserOverrides = res.UserOverrides, + Type = res.Type, + TitleEncoderType = res.TitleEncoderType, + TitleEncryptionKey = res.TitleEncryptionKey, + PictureType = res.PictureType, + Server = this + }; + } + + public async Task GetUser(long UserId, CancellationToken CancellationToken) where Tuser : SocketUser, new() + { + SocketUserSTC user; + if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId)) + { + Tuser temp = poeople.Where(s => s is Tuser && s.Id == UserId).Cast().FirstOrDefault()!; + return temp; + } + while (true) + { + if (CanRequest) + { + user = await GetFromServer("socketuser", + SocketUserSTCContext.Default.SocketUserSTC, + CancellationToken, + new KeyValuePair("id", UserId.ToString())); + break; + } + } + + if (user is null) throw new Exception("Server did not return a user"); + if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId)) + { + foreach (IUser? p in poeople.Where(s => s.Id == UserId)) + { + poeople.Remove(p); + } + } + if (user is null || user.Error is not null) + { + string error = "User was null"; + if (user is not null && user.Error is not null) error = $"{user.Error}: {user.ErrorMessage}"; + throw new Exception($"Something went wrong getting your user information\n{error}"); + } + + Tuser u = new(); + if (typeof(Tuser).FullName == typeof(SocketUser).FullName) + { + u = new() + { + Server = this, + Id = user.ID, + ServerProfile = user.ServerProfile, + RoleIds = user.RoleIds, + Status = user.Status + }; + } + else + { + u = (new SocketAppUser() + { + Server = this, + Id = user.ID, + ServerProfile = user.ServerProfile, + SelectedChannel = user.SelectedChannel, + RoleIds = user.RoleIds, + Status = user.Status, + } as Tuser)!; + } + poeople.Add(u); + return u; + } + + public Tuser GetUser(long UserId) where Tuser : SocketUser, new() + { + SocketUserSTC user; + if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId)) + { + Tuser temp = poeople.Where(s => s is Tuser && s.Id == UserId).Cast().FirstOrDefault()!; + return temp; + } + while (true) + { + if (CanRequest) + { + user = GetFromServerRaw("socketuser", + SocketUserSTCContext.Default.SocketUserSTC, + new KeyValuePair("id", UserId.ToString())); + break; + } + } + + if (user is null) throw new Exception("Server did not return a user"); + if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId)) + { + foreach (IUser? p in poeople.Where(s => s.Id == UserId)) + { + poeople.Remove(p); + } + } + if (user is null || user.Error is not null) + { + string error = "User was null"; + if (user is not null && user.Error is not null) error = $"{user.Error}: {user.ErrorMessage}"; + throw new Exception($"Something went wrong getting your user information\n{error}"); + } + + Tuser u = new(); + if (typeof(Tuser).FullName == typeof(SocketUser).FullName) + { + u = new() + { + Server = this, + Id = user.ID, + ServerProfile = user.ServerProfile, + RoleIds = user.RoleIds, + Status = user.Status + }; + } + else + { + u = (new SocketAppUser() + { + Server = this, + Id = user.ID, + ServerProfile = user.ServerProfile, + SelectedChannel = user.SelectedChannel, + RoleIds = user.RoleIds, + Status = user.Status, + } as Tuser)!; + } + poeople.Add(u); + return u; + } + + public async Task GetProfile(long ProfileId, CancellationToken CancellationToken) + { + ProfileSTC user; + if (profiles.Count > 0 && profiles.Any(s => s.ID == ProfileId)) + { + ServerProfile temp = profiles.Where(s => s.ID == ProfileId).FirstOrDefault()!; + return temp; + } + while (true) + { + if (CanRequest) + { + user = await GetFromServer("socketprofile", + ChannelProfileSTCContext.Default.ProfileSTC, + CancellationToken, + new KeyValuePair("id", ProfileId.ToString())); + break; + } + } + + if (user is null) throw new Exception("Server did not return a user"); + if (profiles.Count > 0 && profiles.Any(s => s.ID == ProfileId)) + { + _ = profiles.RemoveAll(s => s.ID == ProfileId); + } + if (user is null || user.Error is not null) + { + string error = "User was null"; + if (user is not null && user.Error is not null) error = $"{user.Error}: {user.ErrorMessage}"; + throw new Exception($"Something went wrong getting your user information\n{error}"); + } + + ServerProfile u = new() + { + Server = this, + ID = user.ID, + DisplayName = user.DisplayName, + PictureType = user.PictureType, + Controllers = user.Controllers, + RoleControllers = user.RoleControllers + }; + profiles.Add(u); + return u; + } + + public async Task GetMyProfiles(CancellationToken CancellationToken) + { + ProfileListSTC Prof; + while (true) + { + if (CanRequest) + { + Prof = await GetFromServer("socketprofile/myprofiles", + ProfileListSTCContext.Default.ProfileListSTC, + CancellationToken); + break; + } + } + + if (Prof is null) throw new Exception("Server did not return a Profile List"); + List profiles_ = new(); + foreach (ProfileSTC user in Prof.Profiles) + { + if (profiles.Count > 0 && profiles.Any(s => s.ID == user.ID)) + { + _ = profiles.RemoveAll(s => s.ID == user.ID); + } + if (user is null || user.Error is not null) + { + string error = "Profile was null"; + if (user is not null && user.Error is not null) error = $"{user.Error}: {user.ErrorMessage}"; + throw new Exception($"Something went wrong getting your profile information\n{error}"); + } + + ServerProfile u = new() + { + Server = this, + ID = user.ID, + DisplayName = user.DisplayName, + PictureType = user.PictureType, + Controllers = user.Controllers, + RoleControllers = user.RoleControllers + }; + profiles_.Add(u); + profiles.Add(u); + } + + return profiles_.ToArray(); + } + + /// + /// Sends the server a request to update the of you account + /// + /// The you want to set your status to + /// + public async Task UpdateStatus(UserStatus Status, CancellationToken CancellationToken) + { + STC? data = await SendServer("SocketUserProfile/Status", new StatusUpdateCTS() { Status = Status }, StatusUpdateCTSContext.Default.StatusUpdateCTS, STCContext.Default.STC, CancellationToken); + if (data.Error is not null && ((int)data.StatusCode < 200 || (int)data.StatusCode > 299)) + { + if (data?.ErrorMessage is not null) throw new Exception(data.ErrorMessage); + if (data?.Error is not null) throw new Exception(((int)data.Error).ToString()); + else throw new Exception("Something went worng"); + } + + User.Status = Status; + return Task.CompletedTask; + } + + public string Name { get; private set; } + public string Description { get; private set; } +} \ No newline at end of file diff --git a/Luski.net/Server.Encryption.cs b/Luski.net/Server.Encryption.cs new file mode 100644 index 0000000..d8a47f0 --- /dev/null +++ b/Luski.net/Server.Encryption.cs @@ -0,0 +1,83 @@ +using System; +using System.Net.Http; +using System.Security.Cryptography; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.net.Structures; + +namespace Luski.net; + +public class ServerEncryption +{ + internal bool Generating, Generated; + internal string ServerPublicKey = "", MyPublicKey = "", myPrivateKey = "", OfflinePrivateKey = "", OfflinePublicKey = ""; + + internal byte[] Hash = default!; + internal ServerEncryption(ServerStorage Storage) + { + this.Storage = Storage; + } + + public string GetChannelKey(long Channel) + { + return Storage.GetResourceKeyRaw(StorageDirectory.ChannelKeys, Channel.ToString(), Hash); + } + + public LocalKeyInfo GetKey(long Key) + { + return Storage.GetResourceKey(StorageDirectory.ServerKeys, Key.ToString(), Hash); + } + + public void SetKey(long Key, LocalKeyInfo Info) + { + Storage.SetResourceKey(StorageDirectory.ServerKeys, Key.ToString(), Info, Hash); + } + + public void SetChannelKey(long Channel, string Key) + { + Storage.SetResourceKey(StorageDirectory.ChannelKeys, Channel.ToString(), Hash, Key); + } + + private ServerStorage Storage { get; } + + internal int PasswordVersion = 0; + internal byte[] LocalPasswordEncrypt(byte[] Password) => LocalPasswordEncrypt(Password, PasswordVersion); + internal string RemotePasswordEncrypt(byte[] Password) => RemotePasswordEncrypt(Password, PasswordVersion); + + internal byte[] LocalPasswordEncrypt(byte[] Password, int PasswordVersion) + { + return PasswordVersion switch + { + 0 => SHA256.Create().ComputeHash(Password), + _ => throw new ArgumentException("The value provided was not accepted", nameof(PasswordVersion)), + }; + } + + internal string RemotePasswordEncrypt(byte[] Password, int PasswordVersion) + { + return PasswordVersion switch + { + 0 => Convert.ToBase64String(Encryption.RSA.Encrypt(LocalPasswordEncrypt(Password, PasswordVersion), ServerPublicKey)), + _ => throw new ArgumentException("The value provided was not accepted", nameof(PasswordVersion)), + }; + } + + public void GenerateKeys() + { + if (!Generating && !Generated) + { + Generating = true; + GenerateNewKeys(out MyPublicKey, out myPrivateKey); + GenerateNewKeys(out OfflinePublicKey, out OfflinePrivateKey); + Generated = true; + } + } + + public static void GenerateNewKeys(out string Public, out string Private, int KeySize = 4096) + { + using RSACryptoServiceProvider r = new(KeySize); + Private = r.ToXmlString(true); + Public = r.ToXmlString(false); + } +} \ No newline at end of file diff --git a/Luski.net/Server.Events.cs b/Luski.net/Server.Events.cs new file mode 100644 index 0000000..e1cb506 --- /dev/null +++ b/Luski.net/Server.Events.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using Luski.net.Interfaces; + +namespace Luski.net; + +public partial class Server +{ + public event Func? UserStatusUpdate; + + public event Func? OnError; + + internal void Exception(Exception e) + { + if (OnError is not null) OnError.Invoke(e); + } + + internal void StatusUpdate(IUser u1, IUser u2) + { + if (UserStatusUpdate is not null) UserStatusUpdate.Invoke(u1, u2); + } +} \ No newline at end of file diff --git a/Luski.net/Server.Globals.cs b/Luski.net/Server.Globals.cs new file mode 100644 index 0000000..5776ad3 --- /dev/null +++ b/Luski.net/Server.Globals.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes; +using Luski.net.Structures; +using Luski.net.Structures.Main; +using Luski.net.Structures.Public; +using WebSocketSharp; + +namespace Luski.net; + +public partial class Server +{ + public ServerType ServerType { get; internal set; } = ServerType.Public; + public string Domain { get; set; } = default!; + + public bool PrintServerMessages { get; set; } = false; + public string ApiVersion { get; } = "v1"; + internal WebSocket? ServerOut; + internal string? Token = null, Error = null, gen = null; + public bool IsLogedIn => Token is not null; + internal bool CanRequest = false, login = false; + internal List poeople = new(); + internal List profiles = new(); + public ConnectionStatus ConnectionStatus { get; protected set; } = ConnectionStatus.FailedToConnect; +} \ No newline at end of file diff --git a/Luski.net/Server.Incoming.cs b/Luski.net/Server.Incoming.cs new file mode 100644 index 0000000..f749c14 --- /dev/null +++ b/Luski.net/Server.Incoming.cs @@ -0,0 +1,12 @@ +using System; +using WebSocketSharp; + +namespace Luski.net; + +public partial class Server +{ + internal void ServerOut_OnError(object? sender, ErrorEventArgs e) + { + this.Exception(new Exception(e.Message)); + } +} \ No newline at end of file diff --git a/Luski.net/Server.Storage.cs b/Luski.net/Server.Storage.cs new file mode 100644 index 0000000..eba88b6 --- /dev/null +++ b/Luski.net/Server.Storage.cs @@ -0,0 +1,369 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.net.JsonTypes; +using Luski.net.Structures; +using File = System.IO.File; + +namespace Luski.net; + +public class ServerStorage +{ + public static readonly string[] Directories = new[] + { + "Info", + "Assets", + "Channels/Keys", + "Keys", + "Avatars", + "Channels/Icons", + "Channels/Messages", + "StorageInfo", + "Files", + "Channels/Profiles" + }; + + private static readonly int[] CantDelete = new[] + { + (int)StorageDirectory.ServerInfo, + (int)StorageDirectory.ChannelKeys, + (int)StorageDirectory.ServerKeys, + (int)StorageDirectory.StorageInfo + }; + + internal ServerStorage(string Domain) + { + if (!Directory.Exists(JT)) Directory.CreateDirectory(JT); + Location = JT + "/Luski/"; + if (!Directory.Exists(Location)) Directory.CreateDirectory(Location); + Location += "Storage/"; + if (!Directory.Exists(Location)) Directory.CreateDirectory(Location); + Location += "Servers/"; + if (!Directory.Exists(Location)) Directory.CreateDirectory(Location); + Location += Domain.ToLower() + "/"; + if (!Directory.Exists(Location)) Directory.CreateDirectory(Location); + if (!File.Exists(Location + "storage.json")) File.WriteAllText(Location + "storage.json", JsonSerializer.Serialize(new ServerStorageInfo(),ServerStorageInfoContext.Default.ServerStorageInfo)); + RawInfo = JsonSerializer.Deserialize(File.ReadAllText(Location + "storage.json"), + ServerStorageInfoContext.Default.ServerStorageInfo)!; + CacheMode = RawInfo.CacheMode; + StorageID = RawInfo.StorageID; + + for (int i = 0; i < Directories.Length; i++) + { + string full = Location; + string[] spl = Directories[i].Split('/'); + foreach (string d in spl) + { + full += d + "/"; + if (!Directory.Exists(full)) Directory.CreateDirectory(full); + } + } + } + + internal void setid(long id) + { + StorageID = id; + RawInfo.StorageID = id; + File.WriteAllText(Location + "storage.json", JsonSerializer.Serialize(RawInfo,ServerStorageInfoContext.Default.ServerStorageInfo)); + } + + internal ServerStorageInfo RawInfo { get; } + + public TResult GetJson(StorageDirectory Directory, string Resource, bool CreateOnMissing, JsonTypeInfo JsonInfo) where TResult : new() + { + string FilePath = GetResourceDirectory(Directory, Resource); + if (!File.Exists(FilePath)) + { + TResult res = new(); + File.WriteAllText(FilePath, JsonSerializer.Serialize(res, JsonInfo)); + return res; + } + Stream s = GetResourceStream(FilePath); + return JsonSerializer.Deserialize(s, JsonInfo)!; + } + + public void SetJson(StorageDirectory Directory, string Resource, TResult obj, JsonTypeInfo JsonInfo) where TResult : new() + { + string FilePath = GetResourceDirectory(Directory, Resource); + File.WriteAllText(FilePath, JsonSerializer.Serialize(obj, JsonInfo)); + } + + + public byte[] UpdateStorage(byte[] OldPassword) + { + try + { + byte[] NewPassword = new byte[100]; + byte[] lpk = GetResourceBytes(StorageDirectory.StorageInfo, "lpk"); + try + { + OldPassword = Encryption.AES.Decrypt(OldPassword, lpk); + } + catch (Exception e) + { + Console.WriteLine("Attempting to generate a new storage"); + try + { + return GenerateStorage(); + } + catch + { + throw new Exception("Failed To Decrypt Local Storage.", e); + } + } + + using (RandomNumberGenerator provider = RandomNumberGenerator.Create()) + { + provider.GetBytes(NewPassword); + } + void UpdateDir(string Dir) + { + DirectoryInfo DI = new(Dir); + foreach (DirectoryInfo DIR in DI.GetDirectories()) + { + UpdateDir(DIR.FullName); + } + + foreach (FileInfo FI in DI.GetFiles()) + { + byte[] raw = Array.Empty(); + try + { + raw = Encryption.AES.Decrypt( + File.ReadAllBytes(FI.FullName), + OldPassword + ); + } + catch (Exception e) + { + Console.WriteLine("Failed to decrypt file: '{0}'\nError: {1}", FI.FullName, e.ToString()); + FI.Delete(); + continue; + } + try + { + raw = Encryption.AES.Encrypt(raw, + NewPassword + ); + } + catch (Exception e) + { + Console.WriteLine("Failed to encrypt file data: '{0}'\nError: {1}", FI.FullName, e.ToString()); + continue; + } + + try + { + File.WriteAllBytes(FI.FullName, raw); + } + catch (Exception e) + { + Console.WriteLine("Failed to write file data: '{0}'\nError: {1}", FI.FullName, e.ToString()); + } + + } + } + UpdateDir(GetStorageDirectory(StorageDirectory.ServerKeys)); + UpdateDir(GetStorageDirectory(StorageDirectory.ChannelKeys)); + + for (int i = 0; i < Directories.Length; i++) + { + string full = Location; + string[] spl = Directories[i].Split('/'); + if (!RawInfo.DontDelete && !CantDelete.Contains(i)) + { + try + { + if (Directory.Exists(full + Directories[i])) Directory.Delete(full + Directories[i], true); + } + catch + { + // ignored + } + } + + foreach (string d in spl) + { + full += d + "/"; + if (!Directory.Exists(full)) Directory.CreateDirectory(full); + } + } + + SetResourceBytes(StorageDirectory.StorageInfo, "lpk", OldPassword); + + return NewPassword; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + public byte[] GenerateStorage() + { + byte[] NewPassword = new byte[100]; + byte[] OldPassword = new byte[100]; + using (RandomNumberGenerator provider = RandomNumberGenerator.Create()) + { + provider.GetBytes(OldPassword); + } + using (RandomNumberGenerator provider = RandomNumberGenerator.Create()) + { + provider.GetBytes(NewPassword); + } + + for (int i = 0; i < Directories.Length; i++) + { + string full = Location; + string[] spl = Directories[i].Split('/'); + if (!RawInfo.DontDelete && !CantDelete.Contains(i)) + { + try + { + if (Directory.Exists(full + Directories[i])) Directory.Delete(full + Directories[i], true); + } + catch + { + // ignored + } + } + + foreach (string d in spl) + { + full += d + "/"; + if (!Directory.Exists(full)) Directory.CreateDirectory(full); + } + } + + SetResourceBytes(StorageDirectory.StorageInfo, "lpk", OldPassword); + + return NewPassword; + } + + public long StorageID { get; internal set; } + + public string Location { get; internal set; } + + public string GetStorageDirectory(StorageDirectory Directory) + { + return Location + Directories[(byte)Directory] + "/"; + } + + public string GetResourceDirectory(StorageDirectory Directory, string Resourse) + { + return Location + Directories[(byte)Directory] + "/" + Resourse; + } + + public string GetResourceKey(StorageDirectory Directory, string Resource, string Key) + { + return Encoding.UTF8.GetString(Encryption.AES.Decrypt(GetResourceBytes(Directory, Resource), Key)); + } + + public string GetResourceKeyRaw(StorageDirectory Directory, string Resource, byte[] Key) + { + return Encoding.UTF8.GetString(Encryption.AES.Decrypt(GetResourceBytes(Directory, Resource), Key)); + } + + public LocalKeyInfo GetResourceKey(StorageDirectory Directory, string Resource, byte[] Key) + { + string str = + Encoding.UTF8.GetString(Encryption.AES.Decrypt(GetResourceBytes(Directory, Resource), + Key)); + string b = str.Split(':')[0]; + str = str.Remove(0, b.Length + 1); + return new() + { + Key = str, + EncryptionType = (EncryptionType)short.Parse(b) + }; + } + + public void SetResourceKey(StorageDirectory Directory, string Resource, LocalKeyInfo Info, byte[] Key) + { + SetResourceKey(Directory, Resource, Key, $"{(int)Info.EncryptionType}:{Info.Key}"); + } + + public void SetResourceKey(StorageDirectory Directory, string Resource, string Key, string value) + { + File.WriteAllBytes(GetStorageDirectory(Directory) + Resource, Encryption.AES.Encrypt(Encoding.UTF8.GetBytes(value), Key)); + } + public void SetResourceKey(StorageDirectory Directory, string Resource, byte[] Key, string value) + { + File.WriteAllBytes(GetStorageDirectory(Directory) + Resource, Encryption.AES.Encrypt(Encoding.UTF8.GetBytes(value), Key)); + } + + public void SetResourceKey(StorageDirectory Directory, string Resource, byte[] Key, byte[] value) + { + File.WriteAllBytes(GetStorageDirectory(Directory) + Resource, Encryption.AES.Encrypt(value, Key)); + } + + public byte[] GetResourceBytes(StorageDirectory Directory, string Resource) + { + return File.ReadAllBytes(Location + Directories[(byte)Directory] + "/" + Resource); + } + + public Stream GetResourceStream(StorageDirectory Directory, string Resource) + { + return File.OpenRead(Location + Directories[(byte)Directory] + "/" + Resource); + byte[] buffer = new byte[16 * 1024]; + MemoryStream ms = new(); + using (FileStream r = File.OpenRead(Location + Directories[(byte)Directory] + "/" + Resource)) + { + int readBytes; + while ((readBytes = r.Read(buffer, 0, buffer.Length)) > 0) + { + ms.Write(buffer, 0, readBytes); + } + } + + ms.Position = 0; + return ms; + } + + public Stream GetResourceStream(string dir) + { + return File.OpenRead(dir); + byte[] buffer = new byte[16 * 1024]; + MemoryStream ms = new(); + using (FileStream r = File.OpenRead(dir)) + { + int readBytes; + while ((readBytes = r.Read(buffer, 0, buffer.Length)) > 0) + { + ms.Write(buffer, 0, readBytes); + } + } + + ms.Position = 0; + return ms; + } + + public void SetResourceBytes(StorageDirectory Directory, string Resource, byte[] data) + { + File.WriteAllBytes(Location + Directories[(byte)Directory] + "/" + Resource, data); + } + + public CacheMode CacheMode { get; internal set; } + + internal static string JT + { + get + { + string tmp = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "JacobTech"); + if (OperatingSystem.IsLinux()) + { + tmp = Path.Combine(Environment.GetEnvironmentVariable("HOME")!, ".config/"); + tmp += "JacobTech"; + } + return tmp; + } + } +} \ No newline at end of file diff --git a/Luski.net/Server.cs b/Luski.net/Server.cs new file mode 100644 index 0000000..c67ce58 --- /dev/null +++ b/Luski.net/Server.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Net.Mime; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes.BaseTypes; +using Luski.net.JsonTypes.WSS; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using File = System.IO.File; + +namespace Luski.net; + +public partial class Server +{ + + internal Server(string Domain, string API_Version, bool Secure = true) + { + this.Domain = Domain; + this.ApiVersion = API_Version; + this.Secure = Secure; + Storage = new(Domain); + EncryptionHandler = new(Storage); + } + + internal bool Secure = true; + internal string wssurl { get; set; } = ""; + + public ServerEncryption EncryptionHandler { get; } + public ServerStorage Storage { get; } + + public async Task GetAvatar(CancellationToken CancellationToken) + { + bool isc = File.Exists(Storage.GetStorageDirectory(StorageDirectory.ServerAssets) + "Icon"); + if (!isc) await GetFromServer($"socketserver/Icon/", Storage.GetStorageDirectory(StorageDirectory.ServerAssets) + "Icon", CancellationToken); + return Storage.GetResourceStream(StorageDirectory.ServerAssets, "Icon"); + } + + public void SendServerOld(Tvalue Payload, JsonTypeInfo jsonTypeInfo) where Tvalue : IncomingWSS + { + ServerOut?.Send(JsonSerializer.Serialize(Payload, jsonTypeInfo)); + } + + public void SendServer(Luski.Shared.PublicServers.V1.Enums.DataType Type, IServerEvent Payload) + { + ServerOut?.Send(JsonSerializer.Serialize(new WSSOut() + { + Data = new() + { + Type = Type, + Data = Payload + } + })); + } + + + public HttpResponseMessage GetFromServer(string Path, CancellationToken CancellationToken, params KeyValuePair[] Headers) + { + using HttpClient web = new(); + web.Timeout = TimeSpan.FromSeconds(10); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + return web.GetAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", cancellationToken: CancellationToken).Result; + } + + public Task GetFromServer(string Path, string File, CancellationToken CancellationToken, params KeyValuePair[] Headers) + { + using HttpClient web = new(); + web.Timeout = TimeSpan.FromMinutes(10); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + HttpResponseMessage Response = web.GetAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", CancellationToken).Result; + Stream stream = Response.Content.ReadAsStreamAsync(CancellationToken).Result; + using FileStream fs = System.IO.File.Create(File); + stream.CopyTo(fs); + return Task.CompletedTask; + } + + public async Task GetFromServer(string Path, JsonTypeInfo Type, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tresult : STC, new() + { + HttpResponseMessage ServerResponce = GetFromServer(Path, CancellationToken, Headers); + Tresult temp = new(); + string raw = ServerResponce.Content.ReadAsStringAsync(CancellationToken).Result; + try + { + temp = JsonSerializer.Deserialize(raw, Type)!; + } + catch (Exception e) + { + Console.WriteLine("JSON parse failed for the following data as type {0}\n{1}", temp.GetType(), raw); + } + if (temp is null) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; + return temp; + } + + public Tresult GetFromServerRaw(string Path, JsonTypeInfo Type, params KeyValuePair[] Headers) where Tresult : STC, new() + { + HttpResponseMessage ServerResponce = GetFromServer(Path, CancellationToken.None, Headers); + Tresult temp = new(); + string raw =""; + try + { + temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStream(), Type)!; + } + catch (Exception e) + { + Console.WriteLine("JSON parse failed for the following data as type {0}\n{1}", temp.GetType(), raw); + } + if (temp is null) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; + return temp; + } + + public async Task SendServer(string Path, Tvalue Payload, JsonTypeInfo jsonTypeInfo, JsonTypeInfo ReturnjsonTypeInfo, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tvalue : CTS where Tresult : STC, new() + { + using HttpClient web = new(); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + HttpResponseMessage ServerResponce = web.PostAsJsonAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", Payload, jsonTypeInfo, CancellationToken).Result; + //if (!ServerResponce.IsSuccessStatusCode) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with status code {(int)ServerResponce.StatusCode}:{ServerResponce.StatusCode}" }; + Tresult error = new() { StatusCode = ServerResponce.StatusCode, Error = null, ErrorMessage = $"Server responded with empty data" }; + if (string.IsNullOrWhiteSpace(ServerResponce.Content.ReadAsStringAsync(CancellationToken).Result)) return error; + try + { + Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync(CancellationToken).Result, ReturnjsonTypeInfo); + if (temp is null) return error; + return temp; + } + catch { return error; } + } + + public async Task SendServerPatch(string Path, Tvalue Payload, JsonTypeInfo jsonTypeInfo, JsonTypeInfo ReturnjsonTypeInfo, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tvalue : CTS where Tresult : STC, new() + { + using HttpClient web = new(); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + HttpResponseMessage ServerResponce = await web.PatchAsJsonAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", Payload, jsonTypeInfo, CancellationToken); + // HttpResponseMessage ServerResponce = await web.PatchAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", new StringContent(JsonSerializer.Serialize(Payload, jsonTypeInfo)), + // CancellationToken); + //if (!ServerResponce.IsSuccessStatusCode) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with status code {(int)ServerResponce.StatusCode}:{ServerResponce.StatusCode}" }; + Tresult error = new() { StatusCode = ServerResponce.StatusCode, Error = null, ErrorMessage = $"Server responded with empty data" }; + if (string.IsNullOrWhiteSpace(ServerResponce.Content.ReadAsStringAsync(CancellationToken).Result)) return error; + try + { + Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync(CancellationToken).Result, ReturnjsonTypeInfo); + if (temp is null) return error; + return temp; + } + catch { return error; } + } + + public async Task SendServer(string Path, string File, JsonTypeInfo ReturnjsonTypeInfo, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tresult : STC, new() + { + var fs = System.IO.File.OpenRead(File); + try + { + using HttpClient web = new(); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + web.Timeout = new TimeSpan(0, 10, 0); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + //web.DefaultRequestHeaders.Add("Content-Type", MediaTypeNames.Application.Octet); + HttpResponseMessage ServerResponce = web.PostAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", new StreamContent(fs), CancellationToken).Result; + try + { + Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync(CancellationToken).Result, ReturnjsonTypeInfo); + if (temp is null) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = null, ErrorMessage = $"Server responded with empty data" }; + return temp; + } + catch { return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; } + } + finally + { + fs.Close(); + } + } +} \ No newline at end of file diff --git a/Luski.net/Server.old.Globals.cs b/Luski.net/Server.old.Globals.cs new file mode 100755 index 0000000..5d0d5a6 --- /dev/null +++ b/Luski.net/Server.old.Globals.cs @@ -0,0 +1,97 @@ +/*using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes; +using Luski.net.Sockets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using WebSocketSharp; + +namespace Luski.net; + +public sealed partial class Serverold +{ + internal static string JT + { + get + { + string tmp = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "JacobTech"); + if (OperatingSystem.IsLinux()) + { + tmp = Path.Combine(Environment.GetEnvironmentVariable("HOME")!, ".config/"); + tmp += "JacobTech"; + } + return tmp; + } + } + internal static SocketAudioClient? AudioClient = null; + internal static string? Token = null, Error = null; + internal static bool CanRequest = false; + internal static SocketAppUser? _user; + internal static string platform = "win-x64"; + internal static Branch Branch; + internal static double Percent = 0.5; + private static WebSocket? ServerOut; + private static string? gen = null; + private static bool login = false; + + public static SocketServer CurrentServer = null!; + + internal static string Cache + { + get + { + if (gen is null) + { + if (!Directory.Exists(JT)) Directory.CreateDirectory(JT); + string path = JT + "/Luski/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += Branch.ToString() + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += platform + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += "Data/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += _user?.Id + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += "Cache/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += Path.GetRandomFileName() + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + gen = path; + } + if (!Directory.Exists($"{gen}/avatars")) Directory.CreateDirectory($"{gen}/avatars"); + if (!Directory.Exists($"{gen}/channels")) Directory.CreateDirectory($"{gen}/channels"); + return gen; + } + } + internal static List poeople = new(); + internal static List chans { get; set; } = new(); + internal static string GetKeyFilePath + { + get + { + return GetKeyFilePathBr(Branch.ToString()); + } + } + + internal static string GetKeyFilePathBr(string br) + { + if (!Directory.Exists(JT)) Directory.CreateDirectory(JT); + string path = JT + "/Luski/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += br + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += platform + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += "Data/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += _user?.Id + "/"; + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path += "keys.lsk"; + return path; + } +} +*/ \ No newline at end of file diff --git a/Luski.net/Server.old.cs b/Luski.net/Server.old.cs new file mode 100755 index 0000000..025c3ea --- /dev/null +++ b/Luski.net/Server.old.cs @@ -0,0 +1,281 @@ +/*using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes; +using Luski.net.JsonTypes.BaseTypes; +using Luski.net.JsonTypes.HTTP; +using Luski.net.Sockets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; + +namespace Luski.net; + +public sealed partial class Serverold +{ +#pragma warning disable CA1822 // Mark members as static + public async Task SendFriendResult(long user, bool answer, CancellationToken CancellationToken) + { + + FriendRequestResult json = await SendServer(_user!.Servers.First(),"FriendRequestResult", + new FriendRequestResultOut() + { + Id = user, + Result = answer + }, + FriendRequestResultOutContext.Default.FriendRequestResultOut, + FriendRequestResultContext.Default.FriendRequestResult, + CancellationToken); + + if (json is not null && json.Error is null && json.ErrorMessage is null && answer && json.Channel is not null) + { + SocketDMChannel chan = await SocketChannel.GetChannel((long)json.Channel, SocketDMChannelContext.Default.SocketDMChannel, CancellationToken, _user!.Servers.First()); + _ = chan.StartKeyProcessAsync(CancellationToken); + chans.Add(chan); + } + else + { + throw new Exception(json?.Error.ToString()); + } + return SocketUserBase.GetUser(user, SocketRemoteUserContext.Default.SocketRemoteUser, CancellationToken, _user!.Servers.First()).Result; + } + + public async Task SendFriendRequest(long code, CancellationToken CancellationToken) + { + string ccode = Convert.ToBase64String(Encryption.Hash(Encryption.Encoder.GetBytes(code.ToString()))); + FriendRequestResult? json = await SendServer(_user!.Servers.First(),"FriendRequest", new FriendRequest() { code = ccode}, FriendRequestContext.Default.FriendRequest, FriendRequestResultContext.Default.FriendRequestResult, CancellationToken); + + if (json.StatusCode != HttpStatusCode.Accepted) + { + if (json is not null && json.Error is not null) + { + switch ((ErrorCode)(int)json.Error) + { + case ErrorCode.InvalidToken: + throw new Exception("Your current token is no longer valid"); + case ErrorCode.ServerError: + throw new Exception($"Error from server: {json.ErrorMessage}"); + case ErrorCode.InvalidPostData: + throw new Exception("The post data dent to the server is not the correct format. This may be because you app is couropt or you are using the wron API version"); + case ErrorCode.Forbidden: + throw new Exception("You already have an outgoing request or the persone is not real"); + } + } + + if (json is not null && json.Channel is not null) + { + SocketDMChannel chan = await SocketChannel.GetChannel((long)json.Channel, (JsonTypeInfo)SocketDMChannelContext.Default.SocketDMChannel, CancellationToken, _user!.Servers.First()); + _ = chan.StartKeyProcessAsync(CancellationToken); + chans.Add(chan); + } + } + + MainSocketRemoteUser b = await SocketUserBase.GetUser(code, SocketRemoteUserContext.Default.SocketRemoteUser, CancellationToken, _user!.Servers.First()); + if (json.Channel is not null) + b.FriendStatus = FriendStatus.Friends; + else + b.FriendStatus = FriendStatus.PendingOut; + return b; + } + + /// + /// Sends the server a request to update the of you account + /// + /// The you want to set your status to + /// + public async Task UpdateStatus(UserStatus Status, CancellationToken CancellationToken) + { + if (_user is null) throw new Exception("You must login to make a request"); + IncomingHTTP? data = await SendServer(_user!.Servers.First(),"SocketUserProfile/Status", new Status() { UserStatus = Status }, StatusContext.Default.Status, IncomingHTTPContext.Default.IncomingHTTP, CancellationToken); + if (data.Error is not null && ((int)data.StatusCode < 200 || (int)data.StatusCode > 299)) + { + if (data?.ErrorMessage is not null) throw new Exception(data.ErrorMessage); + if (data?.Error is not null) throw new Exception(((int)data.Error).ToString()); + else throw new Exception("Something went worng"); + } + + _user.Status = Status; + return Task.CompletedTask; + } + + public async Task ChangeChannel(long Channel, CancellationToken CancellationToken, SocketServer Server) + { + if (_user is null) throw new Exception("You must login to make a request"); + IncomingHTTP? data = await SendServer(Server, "ChangeChannel", new Channel() { Id = Channel }, ChannelContext.Default.Channel, IncomingHTTPContext.Default.IncomingHTTP, CancellationToken); + if (data.StatusCode != HttpStatusCode.Accepted) + { + if (data?.Error is not null) + { + switch (data.Error) + { + case ErrorCode.InvalidToken: + throw new Exception("Your current token is no longer valid"); + case ErrorCode.ServerError: + throw new Exception("Error from server: " + data.ErrorMessage); + } + } + else throw new Exception("Something went worng"); + } + + _user.SelectedChannel = Channel; + } + + public Task SendMessage(string Message, long Channel, SocketServer Server, CancellationToken CancellationToken, params JsonTypes.File[] Files) + { + try + { + _ = GetChannel(Channel, CancellationToken, Server).Result.SendMessage(Message, CancellationToken, Files); + } + catch (Exception e) + { + if (OnError is not null) _ = OnError.Invoke(e); + else throw e; + } + + return Task.CompletedTask; + } + + public void SetMultiThreadPercent(double num) + { + if (num < 1 || num > 100) throw new Exception("Number must be from 1 - 100"); + Percent = num / 100; + } + + public async Task GetMessage(long MessageId, CancellationToken CancellationToken, SocketServer Server) => await SocketMessage.GetMessage(MessageId, CancellationToken, Server); + + public async Task GetUser(long UserID, CancellationToken CancellationToken, SocketServer Server) => await SocketUserBase.GetUser(UserID, SocketRemoteUserContext.Default.SocketRemoteUser, CancellationToken, Server); + + public async Task GetChannel(long Channel, CancellationToken CancellationToken, SocketServer Server) where TChannel : SocketChannel, new() + { + TChannel Return = new(); + switch (Return) + { + case SocketDMChannel: + Return = (await SocketChannel.GetChannel(Channel, SocketDMChannelContext.Default.SocketDMChannel, CancellationToken, Server) as TChannel)!; + break; + case SocketGroupChannel: + Return = (await SocketChannel.GetChannel(Channel, SocketGroupChannelContext.Default.SocketGroupChannel, CancellationToken, Server) as TChannel)!; + break; + case SocketTextChannel: + Return = (await SocketChannel.GetChannel(Channel, SocketTextChannelContext.Default.SocketTextChannel, CancellationToken, Server) as TChannel)!; + break; + case SocketChannel: + Return = (await SocketChannel.GetChannel(Channel, SocketChannelContext.Default.SocketChannel, CancellationToken, Server) as TChannel)!; + break; + case null: + throw new NullReferenceException(nameof(TChannel)); + default: + throw new Exception("Unknown channel type"); + } + return Return; + } + + + public SocketAppUser CurrentUser + { + get + { + if (_user is null) throw new Exception("You must Login first"); + return _user; + } + } +#pragma warning restore CA1822 // Mark members as static + + private void ServerOut_OnError(object? sender, WebSocketSharp.ErrorEventArgs e) + { + if (OnError is not null) OnError.Invoke(new Exception(e.Message)); + } + + [Obsolete("Move to new Data layout")] + internal static void SendServer(string data) + { + ServerOut?.Send(data); + } + + internal static void SendServer(Tvalue Payload, JsonTypeInfo jsonTypeInfo) where Tvalue : IncomingWSS + { + ServerOut?.Send(JsonSerializer.Serialize(Payload, jsonTypeInfo)); + } + + internal static HttpResponseMessage GetFromServer(SocketServer Server, string Path, CancellationToken CancellationToken, params KeyValuePair[] Headers) + { + using HttpClient web = new(); + web.Timeout = TimeSpan.FromSeconds(10); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + return web.GetAsync($"https://{Server.Domain}/{Server.ApiVersion}/{Path}", cancellationToken: CancellationToken).Result; + } + + internal static Task GetFromServer(SocketServer Server, string Path, string File, CancellationToken CancellationToken, params KeyValuePair[] Headers) + { + using HttpClient web = new(); + web.Timeout = TimeSpan.FromMinutes(10); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + HttpResponseMessage Response = web.GetAsync($"https://{Server.Domain}/{Server.ApiVersion}/{Path}", CancellationToken).Result; + Stream stream = Response.Content.ReadAsStreamAsync(CancellationToken).Result; + using FileStream fs = System.IO.File.Create(File); + stream.CopyTo(fs); + return Task.CompletedTask; + } + + internal static async Task GetFromServer(SocketServer Server, string Path, JsonTypeInfo Type, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tresult : IncomingHTTP, new() + { + HttpResponseMessage ServerResponce = GetFromServer(Server, Path, CancellationToken, Headers); + if (!ServerResponce.IsSuccessStatusCode) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with status code {(int)ServerResponce.StatusCode}:{ServerResponce.StatusCode}" }; + Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync(CancellationToken).Result, Type); + if (temp is null) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; + return temp; + } + + internal static async Task SendServer(SocketServer Server, string Path, Tvalue Payload, JsonTypeInfo jsonTypeInfo, JsonTypeInfo ReturnjsonTypeInfo, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tvalue : IWebRequest where Tresult : IncomingHTTP, new() + { + using HttpClient web = new(); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + HttpResponseMessage ServerResponce = web.PostAsJsonAsync($"https://{Server.Domain}/{Server.ApiVersion}/{Path}", Payload, jsonTypeInfo, CancellationToken).Result; + if (!ServerResponce.IsSuccessStatusCode) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with status code {(int)ServerResponce.StatusCode}:{ServerResponce.StatusCode}" }; + Tresult error = new() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; + if (string.IsNullOrWhiteSpace(ServerResponce.Content.ReadAsStringAsync(CancellationToken).Result)) return error; + try + { + Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync(CancellationToken).Result, ReturnjsonTypeInfo); + if (temp is null) return error; + return temp; + } + catch { return error; } + } + + internal static async Task SendServer(SocketServer Server, string Path, string File, JsonTypeInfo ReturnjsonTypeInfo, CancellationToken CancellationToken, params KeyValuePair[] Headers) where Tresult : IncomingHTTP, new() + { + var fs = System.IO.File.OpenRead(File); + try + { + using HttpClient web = new(); + if (!login) web.DefaultRequestHeaders.Add("token", Token); + web.Timeout = new TimeSpan(0, 10, 0); + if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value); + HttpResponseMessage ServerResponce = web.PostAsync($"https://{Server.Domain}/{Server.ApiVersion}/{Path}", new StreamContent(fs), CancellationToken).Result; + if (!ServerResponce.IsSuccessStatusCode) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with status code {(int)ServerResponce.StatusCode}:{ServerResponce.StatusCode}" }; + try + { + Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync(CancellationToken).Result, ReturnjsonTypeInfo); + if (temp is null) return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; + return temp; + } + catch { return new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" }; } + } + finally + { + fs.Close(); + } + } +} +*/ \ No newline at end of file diff --git a/Luski.net/Structures/File.cs b/Luski.net/Structures/File.cs new file mode 100755 index 0000000..29b7be6 --- /dev/null +++ b/Luski.net/Structures/File.cs @@ -0,0 +1,118 @@ +using Luski.net.Enums; +using Luski.net.JsonTypes.BaseTypes; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using Luski.net.Interfaces; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures; + +public class File : STC +{ + public MainServer Server { get; internal set; } + [JsonInclude] + [JsonPropertyName("name")] + public string Name { get; internal set; } = default!; + [JsonInclude] + [JsonPropertyName("size")] + public ulong Size { get; internal set; } = default!; + [JsonInclude] + [JsonPropertyName("id")] + public long Id { get; internal set; } = default!; + [JsonIgnore] + internal string? key { get; set; } = default!; + [JsonIgnore] + internal string loc { get; set; } = default!; + + public async Task DownloadBytes(string Loc, long key, CancellationToken CancellationToken) + { + //TODO make better + + //using HttpClient web = new(); + //web.DefaultRequestHeaders.Add("token", Server.Token); + //web.DefaultRequestHeaders.Add("id", id.ToString()); + //IncomingHTTP? request = JsonSerializer.Deserialize(web.GetAsync($"https://{Server.Domain}/{Server.API_Ver}/SocketMessage/GetFile").Result.Content.ReadAsStringAsync().Result, IncomingHTTPContext.Default.IncomingHTTP); + string path = Path.GetTempFileName(); + + await Server.GetFromServer($"SocketMessage/GetFile/{Id}", path, CancellationToken); + string Key = (key == 0 ? Server.EncryptionHandler.MyPublicKey : "") ;// ClientEncryption.File.Channels.GetKey(key))!; + Encryption.AES.DecryptToFile(System.IO.File.ReadAllBytes(path), Key, Loc); + /* + if (request is not null && request.Error is not null) + { + switch (request.Error) + { + case ErrorCode.InvalidToken: + throw new Exception("Your current token is no longer valid"); + case ErrorCode.ServerError: + throw new Exception("Error from server: " + request.ErrorMessage); + case ErrorCode.Forbidden: + throw new Exception("Your request was denied by the server"); + default: + MemoryStream? ms = new(); + JsonSerializer.Serialize(new Utf8JsonWriter(ms), + request, + IncomingHTTPContext.Default.IncomingHTTP); + throw new Exception(Encoding.UTF8.GetString(ms.ToArray())); + } + } + + + if (request?.data is not null) + { + foreach (string raw in request.data) + { + Encryption.AES.Decrypt(Convert.FromBase64String(raw), Encryption.File.Channels.GetKey(key), Loc); + } + }*/ + } + + public void SetFile(string path) + { + FileInfo fi = new(path); + Name = fi.Name; + Size = (ulong)fi.Length; + loc = path; + } + + private bool Uploaded = false; + + internal async Task Upload(string keyy, CancellationToken CancellationToken) + { + if (Uploaded) return Id; + if (Name != null) Name = Convert.ToBase64String(Encryption.RSA.Encrypt(Encoding.UTF8.GetBytes(Name), keyy)); + string NPath = Encryption.AES.EncryptFile(loc, keyy); + File sf = await Server.SendServer( + "SocketMessage/UploadFile", + NPath, + FileContext.Default.File, + CancellationToken, + new KeyValuePair("name", Name)); + try { System.IO.File.Delete(NPath); } catch { } + if (sf.Error is not null) throw new Exception(sf.ErrorMessage); + Id = sf.Id; + Uploaded = true; + return sf.Id; + } + + internal void decrypt() + { + if (Name is not null) Name = Encoding.UTF8.GetString(Encryption.RSA.Decrypt(Convert.FromBase64String(Name), key)); + } +} + +[JsonSerializable(typeof(File))] +internal partial class FileContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/LocalKeyInfo.cs b/Luski.net/Structures/LocalKeyInfo.cs new file mode 100644 index 0000000..ae92b7a --- /dev/null +++ b/Luski.net/Structures/LocalKeyInfo.cs @@ -0,0 +1,9 @@ +using JacobTechEncryption.Enums; + +namespace Luski.net.Structures; + +public class LocalKeyInfo +{ + public string Key { get; init; } = default!; + public EncryptionType EncryptionType { get; init; } = default!; +} \ No newline at end of file diff --git a/Luski.net/Structures/Main/MainSocketAppUser.cs b/Luski.net/Structures/Main/MainSocketAppUser.cs new file mode 100755 index 0000000..2a52317 --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketAppUser.cs @@ -0,0 +1,201 @@ +using System; +using Luski.net.Interfaces; +using Luski.net.JsonTypes.BaseTypes; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using Luski.net.Enums; +using Luski.net.Enums.Main; +using Luski.net.JsonTypes; + +namespace Luski.net.Structures.Main; + +public class MainSocketAppUser : MainSocketUserBase, IAppUser +{ + [JsonPropertyName("selected_channel")] + [JsonInclude] + public long SelectedChannel { get; internal set; } = default!; + [JsonPropertyName("email")] + [JsonInclude] + public string Username { get; internal set; } = default!; + [JsonPropertyName("flags")] + [JsonInclude] + public UserFlag Flags { get; internal set; } = default!; + + [JsonIgnore] + public IReadOnlyList Channels + { + get + { + if (_Channels is null && ChannelIdList is not null) + { + if (ChannelIdList.Length != 0) + { + _Channels = new List(); + foreach (long channel in ChannelIdList) + { + try + { + MainSocketChannel s = Server.GetChannel(channel, + MainSocketChannelContext.Default.MainSocketChannel, CancellationToken.None).Result; + Server.chans.Remove(s); + switch (s.Type) + { + case ChannelType.GROUP: + _Channels.Add(Server.GetChannel(channel, + MainSocketGroupChannelContext.Default.MainSocketGroupChannel, CancellationToken.None).Result); + break; + case ChannelType.DM: + _Channels.Add(Server.GetChannel(channel, + MainSocketDMChannelContext.Default.MainSocketDMChannel, CancellationToken.None).Result); + break; + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } + else _Channels = new List(); + } + return _Channels.AsReadOnly(); + } + } + + [JsonIgnore] + public IReadOnlyList FriendRequests + { + get + { + if (_FriendRequests is null || FriendRequestsRaw is not null) + { + _FriendRequests = new(); + if (ChannelIdList.Length != 0 && FriendRequestsRaw is not null) + { + foreach (FR person in FriendRequestsRaw) + { + //_Friends.Add(SocketRemoteUser.GetUser(person)); + long id = person.user_id == Id ? person.from : person.user_id; + MainSocketRemoteUser frq = Server.GetUser(id, CancellationToken.None).Result; + _FriendRequests.Add(frq); + } + } + else _FriendRequests = new(); + } + return _FriendRequests.AsReadOnly(); + } + } + [JsonIgnore] + public IReadOnlyList Friends + { + get + { + if (_Friends is null || FriendIdList is not null) + { + if (ChannelIdList.Length != 0) + { + _Friends = new List(); + foreach (long person in FriendIdList) + { + _Friends.Add(Server.GetUser(person, CancellationToken.None).Result); + } + } + else _Friends = new List(); + } + return _Friends.AsReadOnly(); + } + } + [JsonPropertyName("channels")] + [JsonInclude] + public long[] ChannelIdList { get; internal set; } = default!; + [JsonPropertyName("friends")] + [JsonInclude] + public long[] FriendIdList { get; internal set; } = default!; + [JsonPropertyName("friend_requests")] + [JsonInclude] + public FR[] FriendRequestsRaw { get; internal set; } = default!; + [JsonIgnore] + private List _Channels = null!; + [JsonIgnore] + private List _Friends = default!; + [JsonIgnore] + private List _FriendRequests = default!; + + public class FR + { + public long from { get; set; } = default!; + public long user_id { get; set; } = default!; + } + + internal void AddFriend(MainSocketRemoteUser User) + { + if (Server.poeople.Any(s => s.Id == User.Id)) + { + IEnumerable b = Server.poeople.Where(s => s.Id == User.Id); + foreach (IUser item in b) + { + Server.poeople.Remove(item); + } + Server.poeople.Add(User); + } + else + { + Server.poeople.Add(User); + } + _Friends.Add(User); + } + + internal void RemoveFriendRequest(MainSocketRemoteUser User) + { + if (Server.poeople.Any(s => s.Id == User.Id)) + { + IEnumerable b = Server.poeople.Where(s => s.Id == User.Id); + foreach (IUser item in b) + { + Server.poeople.Remove(item); + } + } + Server.poeople.Add(User); + foreach (MainSocketRemoteUser user in _FriendRequests) + { + if (User.Id == user.Id) + { + _FriendRequests.Remove(User); + } + } + } + + internal void AddFriendRequest(MainSocketRemoteUser User) + { + if (Server.poeople.Any(s => s.Id == User.Id)) + { + IEnumerable b = Server.poeople.Where(s => s.Id == User.Id); + foreach (IUser item in b) + { + Server.poeople.Remove(item); + } + Server.poeople.Add(User); + } + else + { + Server.poeople.Add(User); + } + _FriendRequests.Add(User); + } +} + +[JsonSerializable(typeof(MainSocketAppUser))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never)] +internal partial class MainSocketAppUserContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Main/MainSocketChannel.cs b/Luski.net/Structures/Main/MainSocketChannel.cs new file mode 100755 index 0000000..3fa3c14 --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketChannel.cs @@ -0,0 +1,145 @@ +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes.BaseTypes; +using System.Collections.Generic; +using System; +using System.Text.Json.Serialization; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Luski.net.JsonTypes.WSS; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums.Main; +using Luski.net.JsonTypes; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures.Main; + +public class MainSocketChannel : STC +{ + [JsonInclude] + [JsonPropertyName("id")] + public long Id { get; internal set; } = default!; + [JsonInclude] + [JsonPropertyName("title")] + public string Title { get; internal set; } = default!; + [JsonInclude] + [JsonPropertyName("description")] + public string Description { get; internal set; } = default!; + [JsonInclude] + [JsonPropertyName("key")] + public string Key { get; internal set; } = default!; + [JsonPropertyName("type")] + [JsonInclude] + public ChannelType Type { get; internal set; } = default!; + + public MainServer Server { get; internal set; } = default!; + + [JsonPropertyName("members")] + [JsonInclude] + public long[] MemberIdList { get; internal set; } = default!; + [JsonIgnore] + public IReadOnlyList Members + { + get + { + if (MemberIdList is null || MemberIdList.Length == 0) return Array.Empty().ToList().AsReadOnly(); + if (_members is null || !_members.Any()) + { + _members = new(); + foreach (long member in MemberIdList) + { + if (member != Server.User.Id) _members.Add(Server.GetUser(member, CancellationToken.None).Result); + else _members.Add(Server.User); + } + } + return _members.AsReadOnly(); + } + } + [JsonIgnore] + private List _members = new(); + + public Task SendKeysToUsers(CancellationToken CancellationToken) + { + if (Key is null) + { + StartKeyProcessAsync(CancellationToken).Wait(); + return Task.CompletedTask; + } + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 50) * 2.0)); + if (num == 0) num = 1; + string lkey = Server.EncryptionHandler.GetChannelKey(Id); + Parallel.ForEach(Members, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + if (i.Id != Server.User.Id) + { + long key = i.GetUserKeys(CancellationToken).Result.First().Id; + if (true) + { + WSSKeyExchange send = new() + { + to = i.Id, + channel = Id, + key = Convert.ToBase64String(Encryption.RSA.Encrypt(lkey, key.ToString(), EncoderType.UTF8)) + }; + Server.SendServerOld(send, WSSKeyExchangeContext.Default.WSSKeyExchange); + } + } + }); + return Task.CompletedTask; + } + + internal Task StartKeyProcessAsync(CancellationToken CancellationToken) + { + //TODO code key exchange + /* + ClientEncryption.GenerateNewKeys(out string Public, out string Private); + Key = Public; + HttpResponseMessage b; + using (HttpClient web = new()) + { + web.DefaultRequestHeaders.Add("token", Luski.net.Server.Token); + b = web.PostAsync($"https://{Server.Domain}/{Server.ApiVersion}/SocketChannel/SetKey/{Id}", new StringContent(Key), CancellationToken).Result; + } + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * Luski.net.Server.Percent) * 2.0)); + if (num == 0) num = 1; + Encryption.File.Channels.AddKey(Id, Private); + Parallel.ForEach(Members, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + if (i.Id != Luski.net.Server._user?.Id) + { + string key = i.GetUserKey(CancellationToken, Server).Result; + if (!string.IsNullOrEmpty(key)) + { + WSSKeyExchange send = new() + { + to = i.Id, + channel = Id, + key = Convert.ToBase64String(Encryption.Encrypt(Private, key)) + }; + Luski.net.Server.SendServer(send, WSSKeyExchangeContext.Default.WSSKeyExchange); + } + } + });*/ + return Task.CompletedTask; + } +} + +[JsonSerializable(typeof(MainSocketChannel))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class MainSocketChannelContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/Luski.net/Structures/Main/MainSocketDMChannel.cs b/Luski.net/Structures/Main/MainSocketDMChannel.cs new file mode 100755 index 0000000..8e9b77f --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketDMChannel.cs @@ -0,0 +1,34 @@ +using Luski.net.JsonTypes.BaseTypes; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; + +namespace Luski.net.Structures.Main; + +public class MainSocketDMChannel : MainSocketTextChannel +{ + public MainSocketRemoteUser User + { + get + { + if (_user is null) + { + var list = MemberIdList.ToList(); + list.Remove(Server.User.Id); + _user = Server.GetUser(list.FirstOrDefault(), CancellationToken.None).Result; + } + return _user; + } + } + public MainSocketRemoteUser _user = null!; +} + +[JsonSerializable(typeof(MainSocketDMChannel))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class MainSocketDMChannelContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Main/MainSocketGroupChannel.cs b/Luski.net/Structures/Main/MainSocketGroupChannel.cs new file mode 100755 index 0000000..1e865d2 --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketGroupChannel.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace Luski.net.Structures.Main; + +public class MainSocketGroupChannel : MainSocketTextChannel +{ + [JsonPropertyName("owner")] + [JsonInclude] + public long Owner { get; internal set; } = default!; +} + +[JsonSerializable(typeof(MainSocketGroupChannel))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class MainSocketGroupChannelContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Main/MainSocketMessage.cs b/Luski.net/Structures/Main/MainSocketMessage.cs new file mode 100755 index 0000000..e59a425 --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketMessage.cs @@ -0,0 +1,60 @@ +using Luski.net.Interfaces; +using Luski.net.JsonTypes.BaseTypes; +using System; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures.Main; + +public class MainSocketMessage : STC +{ + public MainServer Server { get; internal set; } = default!; + [JsonPropertyName("id")] + [JsonInclude] + public long Id { get; internal set; } = default!; + [JsonPropertyName("user_id")] + [JsonInclude] + public long AuthorID { get; internal set; } = default!; + [JsonPropertyName("content")] + [JsonInclude] + public string Context { get; internal set; } = default!; + [JsonPropertyName("channel_id")] + [JsonInclude] + public long ChannelID { get; internal set; } = default!; + [JsonPropertyName("files")] + [JsonInclude] + public File[]? Files { get; internal set; } = default!; + public async Task GetAuthor(CancellationToken CancellationToken) + { + if (Server.User.Id != AuthorID) return await Server.GetUser(AuthorID, CancellationToken); + else return Server.User; + } + + internal void decrypt(string? key, CancellationToken CancellationToken) + { + if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); + Context = Encoding.UTF8.GetString(Encryption.AES.Decrypt(Convert.FromBase64String(Context), key)); + if (Files is not null && Files.Length > 0) + { + for (int i = 0; i < Files.Length; i++) + { + Files[i].key = key; + Files[i].decrypt(); + } + } + } +} + +[JsonSerializable(typeof(MainSocketMessage))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +public partial class MainSocketMessageContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Main/MainSocketRemoteUser.cs b/Luski.net/Structures/Main/MainSocketRemoteUser.cs new file mode 100755 index 0000000..83bdd63 --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketRemoteUser.cs @@ -0,0 +1,37 @@ +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes.BaseTypes; +using System; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Luski.net.Enums.Main; +using Luski.net.Structures; +using Luski.net.Structures.Main; + +namespace Luski.net.Structures.Main; + +public class MainSocketRemoteUser : MainSocketUserBase +{ + [JsonPropertyName("friend_status")] + [JsonInclude] + public FriendStatus FriendStatus { get; internal set; } = default!; + public MainSocketDMChannel Channel { get; internal set; } + + internal MainSocketRemoteUser Clone() + { + return (MainSocketRemoteUser)MemberwiseClone(); + } +} + +[JsonSerializable(typeof(MainSocketRemoteUser))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class MainSocketRemoteUserContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Main/MainSocketTextChannel.cs b/Luski.net/Structures/Main/MainSocketTextChannel.cs new file mode 100755 index 0000000..6a2a64b --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketTextChannel.cs @@ -0,0 +1,181 @@ +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes.BaseTypes; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.Shared.PublicServers.V1.ClientToServer.HTTP; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using ChannelType = Luski.net.Enums.Main.ChannelType; + +namespace Luski.net.Structures.Main; + +public class MainSocketTextChannel : MainSocketChannel +{ + public async Task GetMessage(long ID, CancellationToken CancellationToken) + { + return await Server.GetMessage(ID, CancellationToken); + } + + public async Task GetPicture(CancellationToken CancellationToken) + { + throw new NotImplementedException(); + //if (Type == ChannelType.DM) return Members.First().GetAvatar(CancellationToken).Result; + // else + { + bool isc = System.IO.File.Exists(Server.Storage.GetStorageDirectory(StorageDirectory.ChannelIcons) + Id.ToString()); + if (!isc) await Server.GetFromServer($"SocketChannel/GetPicture/{Id}", Server.Storage.GetStorageDirectory(StorageDirectory.ChannelIcons) + Id.ToString(), CancellationToken); + return Server.Storage.GetResourceStream(StorageDirectory.ChannelIcons, Id.ToString()); + } + } + + public async Task> GetMessages(long Message_Id, CancellationToken CancellationToken, int count = 50) + { + if (count > 200) + { + throw new Exception("You can not request more than 200 messages at a time"); + } + else if (count < 1) + { + throw new Exception("You must request at least 1 message"); + } + else + { + SocketBulkMessage data = await Server.GetFromServer("SocketBulkMessage", + SocketBulkMessageContext.Default.SocketBulkMessage, + CancellationToken, + new KeyValuePair("channel_id", Id.ToString()), + new KeyValuePair("messages", count.ToString()), + new KeyValuePair("mostrecentid", Message_Id.ToString())); + if (data.Error is null) + { + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 5) * 2.0)); + if (num == 0) num = 1; + + string key = Server.EncryptionHandler.GetChannelKey(Id); + if (data is null) throw new Exception("Invalid data from server"); + if (data.Messages is null) data.Messages = Array.Empty(); + Parallel.ForEach(data.Messages, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + i.decrypt(key, CancellationToken); + }); + key = null; + return await Task.FromResult(data.Messages.ToList().AsReadOnly() as IReadOnlyList); + } + else + { + throw new Exception(data.ErrorMessage); + } + } + } + + public async Task> GetMessages(CancellationToken CancellationToken, int count = 50) + { + try + { + if (count > 200) + { + throw new Exception("You can not request more than 200 messages at a time"); + } + else if (count < 1) + { + throw new Exception("You must request at least 1 message"); + } + else + { + DateTime start = DateTime.Now; + SocketBulkMessage data = await Server.GetFromServer("SocketBulkMessage", + SocketBulkMessageContext.Default.SocketBulkMessage, + CancellationToken, + new KeyValuePair("id", Id.ToString()), + new KeyValuePair("messages", count.ToString())); + Console.WriteLine($"Messages downloaded in {(DateTime.Now - start).TotalSeconds}"); + start = DateTime.Now; + if (data is not null && !data.Error.HasValue) + { + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 5) * 2.0)); + if (num == 0) num = 1; + string key = Server.EncryptionHandler.GetChannelKey(Id); + if (data.Messages is null) data.Messages = Array.Empty(); + Parallel.ForEach(data.Messages, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + i.decrypt(key, CancellationToken); + }); + key = null; + Console.WriteLine($"Messages decrypted in {(DateTime.Now - start).TotalSeconds}"); + return await Task.FromResult(data.Messages.ToList().AsReadOnly() as IReadOnlyList); + } + else + { + throw data?.Error switch + { + ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"), + ErrorCode.ServerError => new Exception($"Error from server: {data.ErrorMessage}"), + ErrorCode.InvalidHeader => new Exception(data.ErrorMessage), + ErrorCode.MissingHeader => new Exception("The header sent to the server was not found. This may be because you app is couropt or you are using the wron API version"), + ErrorCode.Forbidden => new Exception("You are not allowed to do this request"), + _ => new Exception(data?.Error.ToString()), + }; + } + } + } + catch (Exception) + { + throw; + } + } + + public async Task SendMessage(string Message, CancellationToken CancellationToken, params File?[] Files) + { + string key = Server.EncryptionHandler.GetChannelKey(Id); + MessageCTS m = new() + { + Base64Context = Convert.ToBase64String(Encryption.RSA.Encrypt(Message, key, EncoderType.UTF8)), + ChannelID = Id, + Profile = null, + EncryptionKey = 0, + Encoding = EncoderType.UTF8 + }; + if (Files is not null && Files.Length > 0) + { + List bb = new(); + for (int i = 0; i < Files.Length; i++) + { + File? ff = Files[i]; + if (ff is not null) + { + bb.Add(await ff.Upload(key, CancellationToken)); + Files[i] = null; + } + } + m.Files = bb.ToArray(); + } + STC data = await Server.SendServer("socketmessage", m, MessageCTSContext.Default.MessageCTS, STCContext.Default.STC, CancellationToken); + if (data.Error is not null && data.ErrorMessage != "Server responded with empty data") throw new Exception(data.ErrorMessage); + return Task.CompletedTask; + } +} + +[JsonSerializable(typeof(MainSocketTextChannel))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false)] +internal partial class MainSocketTextChannelContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Main/MainSocketUserBase.cs b/Luski.net/Structures/Main/MainSocketUserBase.cs new file mode 100644 index 0000000..4183825 --- /dev/null +++ b/Luski.net/Structures/Main/MainSocketUserBase.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.Structures.Public; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures.Main; + +public class MainSocketUserBase : STC, IUser +{ + public MainServer Server { get; internal set; } = default!; + [JsonPropertyName("id")] + [JsonInclude] + public long Id { get; internal set; } = default!; + [JsonPropertyName("username")] + [JsonInclude] + public string DisplayName { get; internal set; } = default!; + [JsonPropertyName("status")] + [JsonInclude] + public UserStatus Status { get; internal set; } = default!; + [JsonPropertyName("picture_type")] + [JsonInclude] + public PictureType PictureType { get; internal set; } = default!; + + public async Task GetAvatar(CancellationToken CancellationToken) + { + bool isc = System.IO.File.Exists(Server.Storage.GetStorageDirectory(StorageDirectory.Avatars) + Id.ToString()); + if (!isc) await Server.GetFromServer($"socketuserprofile/Avatar/{Id}", Server.Storage.GetStorageDirectory(StorageDirectory.Avatars) + Id.ToString(), CancellationToken); + return Server.Storage.GetResourceStream(StorageDirectory.Avatars, Id.ToString()); + } + + public Task GetUserKeys(CancellationToken CancellationToken) + { + string data = Server.GetFromServer($"Keys/GetUserKey/{Id}", CancellationToken).Content.ReadAsStringAsync().Result; + return Task.FromResult(new[] { new PublicKeyInfo() { Id = long.Parse(data) }}); + } +} + +[JsonSerializable(typeof(MainSocketUserBase))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never)] +internal partial class MainSocketUserBaseContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/Luski.net/Structures/Main/SocketBulkMessage.cs b/Luski.net/Structures/Main/SocketBulkMessage.cs new file mode 100755 index 0000000..f936006 --- /dev/null +++ b/Luski.net/Structures/Main/SocketBulkMessage.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures.Main; + +internal class SocketBulkMessage : STC + +{ + [JsonPropertyName("messages")] + [JsonInclude] + public MainSocketMessage[]? Messages { get; set; } = default!; +} + +[JsonSerializable(typeof(SocketBulkMessage))] +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Default, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never)] +internal partial class SocketBulkMessageContext : JsonSerializerContext +{ + +} diff --git a/Luski.net/Structures/Public/Color.cs b/Luski.net/Structures/Public/Color.cs new file mode 100644 index 0000000..1d01bd9 --- /dev/null +++ b/Luski.net/Structures/Public/Color.cs @@ -0,0 +1,60 @@ +using System; + +namespace Luski.net.Structures.Public; + +public struct Color +{ + public Color(string servercol) + { + byte[] t = Convert.FromHexString(servercol); + r = t[0]; + g = t[1]; + b = t[2]; + a = t[3]; + } + + public static bool operator ==(Color a, Color b) + { + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; + } + + public static bool operator !=(Color a, Color b) + { + return a.r != b.r || a.g != b.g || a.b != b.b || a.a != b.a; + } + + public Color(byte R, byte G, byte B, byte A) + { + r = R; + g = G; + b = B; + a = A; + } + + public string ToDatabaseStr() + { + return Convert.ToHexString(new []{r,g,b,a}); + } + + private byte r; + private byte g; + private byte b; + private byte a; + + public byte A + { + get => a; + } + public byte R + { + get => r; + } + public byte G + { + get => g; + } + public byte B + { + get => b; + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/Role.cs b/Luski.net/Structures/Public/Role.cs new file mode 100644 index 0000000..b201e89 --- /dev/null +++ b/Luski.net/Structures/Public/Role.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.Structures.Public; + +public class Role +{ + public required PublicServer Server { get; init; } = default!; + public required long ID { get; init; } = default!; + public int Index { get; internal set; } = default!; + public ColorType ColorType { get; internal set; } = ColorType.Full; + public Color[] Colors { get; internal set; } = default!; + public string Description { get; internal set; } = default!; + public string DisplayName { get; internal set; } = default!; + public ServerPermission ServerPermissions { get; internal set; } = default!; + public long[] MLID = default!; + + public long[] MembersListID + { + get => MLID; + internal set + { + MLID = value; + RawUsers = null; + } + } + private List? RawUsers = null; + + public async Task GetMembers() + { + if (RawUsers is null || RawUsers.Count != MembersListID.Length) + { + RawUsers = new(); + foreach (long m in MembersListID) + { + RawUsers.Add(await Server.GetUser(m, CancellationToken.None)); + } + } + + return RawUsers!.ToArray(); + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/RoleOverride.cs b/Luski.net/Structures/Public/RoleOverride.cs new file mode 100644 index 0000000..17f52a3 --- /dev/null +++ b/Luski.net/Structures/Public/RoleOverride.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.Shared; + +namespace Luski.net.Structures.Public; + +public class RoleOverride +{ + public long ParentRoleID { get; init; } + + public ServerPermission GoodPermissions { get; set; } + + public ServerPermission BadPermissions { get; set; } + + public required PublicServer Server { get; init; } + + public async Task GetRole() + { + return await Server.GetRole(ParentRoleID); + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/ServerProfile.cs b/Luski.net/Structures/Public/ServerProfile.cs new file mode 100644 index 0000000..92af256 --- /dev/null +++ b/Luski.net/Structures/Public/ServerProfile.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.Enums; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.Structures.Public; + +public class ServerProfile +{ + public PublicServer Server { get; init; } = default!; + public long ID { get; init; } = default!; + public string DisplayName { get; init; } = default!; + public PictureType? PictureType { get; init; } = default!; + public long[] Controllers { get; internal set; } = default!; + public long[] RoleControllers { get; internal set; } = default!; + + public async Task GetAvatar(CancellationToken CancellationToken) + { + bool isc = System.IO.File.Exists(Server.Storage.GetStorageDirectory(StorageDirectory.ProfileAvatars) + ID.ToString()); + if (!isc) await Server.GetFromServer($"socketprofile/Avatar/{ID}", Server.Storage.GetStorageDirectory(StorageDirectory.ProfileAvatars) + ID.ToString(), CancellationToken); + return Server.Storage.GetResourceStream(StorageDirectory.ProfileAvatars, ID.ToString()); + } + + public Task GetUserKeys(CancellationToken CancellationToken) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/SocketAppUser.cs b/Luski.net/Structures/Public/SocketAppUser.cs new file mode 100755 index 0000000..68cd69c --- /dev/null +++ b/Luski.net/Structures/Public/SocketAppUser.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.Shared; + +namespace Luski.net.Structures.Public; + +public class SocketAppUser : SocketUser +{ + public long SelectedChannel { get; init; } = default!; + + private List? _serverProfiles; + + public async Task GetSelectedChannel(CancellationToken Token) + { + return await Server.GetChannel(SelectedChannel, Token); + } + + public async Task GetProfiles(CancellationToken Token) + { + if (_serverProfiles is null) + { + _serverProfiles = (await Server.GetMyProfiles(Token)).ToList(); + } + return _serverProfiles.ToArray(); + } + + public async Task HasAccessToCategory(SocketCategory Category, ServerPermission RequiredPerms = ServerPermission.ViewThis) + { + if (Category.Server != Server) return false; + if (Server.OwnerID == Id) return true; + Role[] UserRoleIDList = await GetRoles(); + RequiredPerms |= ServerPermission.ViewThis; + + ServerPermission GoodPerms = ServerPermission.None; + + UserOverride[] CatUserOverides = await Category.GetUserOverrides(); + foreach (UserOverride CatUserOveride in CatUserOverides) + { + if (CatUserOveride.UserID != Id) continue; + if ((CatUserOveride.BadPermissions & RequiredPerms) > ServerPermission.None) return false; + if ((CatUserOveride.GoodPermissions & RequiredPerms) == RequiredPerms) return true; + + GoodPerms |= CatUserOveride.GoodPermissions; + break; + } + + RoleOverride[] CatRoleOverides = await Category.GetRoleOverrides(); + int bad_index = -1; + int good_index = -1; + + foreach (RoleOverride CatRoleOveride in CatRoleOverides) + { + if (!RoleIds.Contains(CatRoleOveride.ParentRoleID)) continue; + var index = (await CatRoleOveride.GetRole()).Index; + if ((CatRoleOveride.BadPermissions & RequiredPerms) > ServerPermission.None) + { + if (bad_index < index) + bad_index = index; + } + else good_index = index; + + GoodPerms |= CatRoleOveride.GoodPermissions; + } + + if (bad_index > good_index) return false; + + foreach (Role RoleID in UserRoleIDList) + { + GoodPerms |= RoleID.ServerPermissions; + } + return GoodPerms.HasPermission(RequiredPerms); + } + + public string Username { get; internal set; } = default!; +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/SocketCategory.cs b/Luski.net/Structures/Public/SocketCategory.cs new file mode 100644 index 0000000..9f49dd5 --- /dev/null +++ b/Luski.net/Structures/Public/SocketCategory.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption.Enums; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures.Public; + +public class SocketCategory +{ + public PublicServer Server { get; init; } = default!; + public long ID { get; init; } + internal long ParentID { get; set; } + internal RoleOverrideSTC[] RoleOverides { get; set; } + internal UserOverrideSTC[] UserOverides { get; set; } + internal long[] Channels { get; set; } + internal long[] Categories { get; set; } + SocketCategory? RawParent = null; + List? RawRoleOverides = null; + List? RawUserOverides = null; + List? RawChan = null; + List? RawCat = null; + + public async Task GetParent() + { + if (ParentID != -1 && RawParent is null) + { + RawParent = await Server.GetCategory(ParentID, CancellationToken.None); + } + + return RawParent; + } + + public async Task GetChannels() + { + if (RawChan is null) + { + RawChan = new(); + foreach (long chan in Channels) + { + RawChan.Add(await Server.GetChannel(chan, CancellationToken.None)); + } + } + + if (RawChan.Count != Channels.Length) + { + foreach (long chan in Channels) + { + if (RawChan.Any(s => s.ID == chan)) continue; + RawChan.Add(await Server.GetChannel(chan, CancellationToken.None)); + } + } + + return RawChan.ToArray(); + } + public async Task GetCategories() + { + if (RawCat is null) + { + RawCat = new(); + foreach (long chan in Categories) + { + RawCat.Add(await Server.GetCategory(chan, CancellationToken.None)); + } + } + + if (RawCat.Count != Channels.Length) + { + foreach (long chan in Categories) + { + if (RawCat.Any(s => s.ID == chan)) continue; + RawCat.Add(await Server.GetCategory(chan, CancellationToken.None)); + } + } + + return RawCat.ToArray(); + } + public string Name { get; internal set; } + public string Description { get; internal set; } + + public Task GetRoleOverrides() + { + if (RawRoleOverides is null) + { + RawRoleOverides = new(); + } + + return Task.FromResult(RawRoleOverides!.ToArray()); + } + public Task GetUserOverrides() + { + if (RawUserOverides is null) + { + RawUserOverides = new(); + } + + return Task.FromResult(RawUserOverides!.ToArray()); + } + public long TitleEncryptionKey { get; internal set; } + public long DescriptionEncryptionKey { get; internal set; } + public EncoderType TitleEncoderType { get; internal set; } + public EncoderType DescriptionEncoderType { get; internal set; } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/SocketChannel.cs b/Luski.net/Structures/Public/SocketChannel.cs new file mode 100644 index 0000000..301a027 --- /dev/null +++ b/Luski.net/Structures/Public/SocketChannel.cs @@ -0,0 +1,442 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; + +namespace Luski.net.Structures.Public; + +public class SocketChannel +{ + public PublicServer Server { get; init; } = default!; + public long ID { get; internal set; } + internal long CategoryID { get; set; } + internal RoleOverrideSTC[] RoleOverrides { get; set; } + internal UserOverrideSTC[] UserOverrides { get; set; } + SocketCategory? RawParent = null; + List? RawRoleOverides = null; + List? RawUserOverides = null; + public PictureType PictureType { get; internal set; } + + public async Task> GetMessages(CancellationToken CancellationToken, SocketMessage Last, int count = 50) + { + try + { + if (count > 200) + { + throw new Exception("You can not request more than 200 messages at a time"); + } + else if (count < 1) + { + throw new Exception("You must request at least 1 message"); + } + else + { + SocketBulkMessageSTC data = await Server.GetFromServer("SocketBulkMessage", + SocketBulkMessageSTCContext.Default.SocketBulkMessageSTC, + CancellationToken, + new KeyValuePair("id", ID.ToString()), + new KeyValuePair("mostrecentid", Last.ID.ToString()), + new KeyValuePair("messages", count.ToString())); + if (data is not null && !data.Error.HasValue) + { + int num = Convert.ToInt32(6); + if (num == 0) num = 1; + if (data.Messages is null) data.Messages = Array.Empty(); + List mmmm = new(); + ParallelLoopResult p = Parallel.ForEach(data.Messages, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + if (i.EncryptionKey == 0) + { + if (string.IsNullOrEmpty(i.Context)) + { + i.Context = ""; + } + else i.Context = Encryption.Generic.Encoders[(int)i.EncoderType] + .GetString(Convert.FromBase64String(i.Context)); + } + else + { + LocalKeyInfo key = Server.EncryptionHandler.GetKey(i.EncryptionKey); + switch (key.EncryptionType) + { + case EncryptionType.RSA: + i.Context = Encryption.RSA.Decrypt(Convert.FromBase64String(i.Context), key.Key, + i.EncoderType); + break; + default: + i.Context = Encryption.Generic.Encoders[(int)i.EncoderType] + .GetString(Convert.FromBase64String(i.Context)); + break; + } + } + + if (i.Files.Length > 0) + { + for (int j = 0; j < i.Files.Length; j++) + { + if (i.Files[j].Key == 0) + { + if (string.IsNullOrEmpty(i.Files[j].Name)) + { + i.Files[j].Name = ""; + } + else i.Files[j].Name = Encryption.Generic.Encoders[(int)i.Files[j].NameEncoder] + .GetString(Convert.FromBase64String(i.Files[j].Name)); + } + else + { + LocalKeyInfo key = Server.EncryptionHandler.GetKey(i.Files[j].NameKey); + switch (key.EncryptionType) + { + case EncryptionType.RSA: + i.Files[j].Name = Encryption.RSA.Decrypt(Convert.FromBase64String(i.Context), key.Key, + i.Files[j].NameEncoder); + break; + default: + i.Files[j].Name = Encryption.Generic.Encoders[(int)i.Files[j].NameEncoder] + .GetString(Convert.FromBase64String(i.Context)); + break; + } + } + } + } + }); + + foreach (MessageSTC i in data.Messages) + { + var ff = new List(); + List sf = new(); + foreach (ServerFileInfoSTC v in i.Files) + { + sf.Add(new() + { + ID = v.ID, + Size = v.Size, + Name = v.Name, + Encoder = v.Encoder, + NameEncoder = v.NameEncoder, + Key = v.Key, + NameKey = v.NameKey, + Server = Server + }); + ff.Add(v.ID); + } + mmmm.Add(new() + { + Server = Server, + ID = i.ID, + TimeStamp = i.Timestamp, + ChannelID = ID, + AuthorID = i.AuthorID, + Context = i.Context, + EncryptionKey = i.EncryptionKey, + EncoderType = i.EncoderType, + FileIDs = ff.ToArray(), + _Files = sf, + ProfileID = i.ProfileID + }); + } + return await Task.FromResult(mmmm.AsReadOnly()); + } + else + { + throw data?.Error switch + { + ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"), + ErrorCode.ServerError => new Exception($"Error from server: {data.ErrorMessage}"), + ErrorCode.InvalidHeader => new Exception(data.ErrorMessage), + ErrorCode.MissingHeader => new Exception("The header sent to the server was not found. This may be because you app is couropt or you are using the wron API version"), + ErrorCode.Forbidden => new Exception("You are not allowed to do this request"), + _ => new Exception(data?.Error.ToString()), + }; + } + } + } + catch (Exception) + { + throw; + } + } + + public async Task> GetMessages(CancellationToken CancellationToken, int count = 50) + { + try + { + if (count > 200) + { + throw new Exception("You can not request more than 200 messages at a time"); + } + else if (count < 1) + { + throw new Exception("You must request at least 1 message"); + } + else + { + SocketBulkMessageSTC data = await Server.GetFromServer("SocketBulkMessage", + SocketBulkMessageSTCContext.Default.SocketBulkMessageSTC, + CancellationToken, + new KeyValuePair("id", ID.ToString()), + new KeyValuePair("messages", count.ToString())); + if (data is not null && !data.Error.HasValue) + { + int num = Convert.ToInt32(6); + if (num == 0) num = 1; + if (data.Messages is null) data.Messages = Array.Empty(); + List mmmm = new(); + ParallelLoopResult p = Parallel.ForEach(data.Messages, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + if (i.EncryptionKey == 0) + { + if (string.IsNullOrEmpty(i.Context)) + { + i.Context = ""; + } + else i.Context = Encryption.Generic.Encoders[(int)i.EncoderType] + .GetString(Convert.FromBase64String(i.Context)); + } + else + { + LocalKeyInfo key = Server.EncryptionHandler.GetKey(i.EncryptionKey); + switch (key.EncryptionType) + { + case EncryptionType.RSA: + i.Context = Encryption.RSA.Decrypt(Convert.FromBase64String(i.Context), key.Key, + i.EncoderType); + break; + default: + i.Context = Encryption.Generic.Encoders[(int)i.EncoderType] + .GetString(Convert.FromBase64String(i.Context)); + break; + } + } + + if (i.Files.Length > 0) + { + for (int j = 0; j < i.Files.Length; j++) + { + if (i.Files[j].Key == 0) + { + if (string.IsNullOrEmpty(i.Files[j].Name)) + { + i.Files[j].Name = ""; + } + else i.Files[j].Name = Encryption.Generic.Encoders[(int)i.Files[j].NameEncoder] + .GetString(Convert.FromBase64String(i.Files[j].Name)); + } + else + { + LocalKeyInfo key = Server.EncryptionHandler.GetKey(i.Files[j].NameKey); + switch (key.EncryptionType) + { + case EncryptionType.RSA: + i.Files[j].Name = Encryption.RSA.Decrypt(Convert.FromBase64String(i.Context), key.Key, + i.Files[j].NameEncoder); + break; + default: + i.Files[j].Name = Encryption.Generic.Encoders[(int)i.Files[j].NameEncoder] + .GetString(Convert.FromBase64String(i.Context)); + break; + } + } + } + } + }); + + foreach (MessageSTC i in data.Messages) + { + var ff = new List(); + List sf = new(); + foreach (ServerFileInfoSTC v in i.Files) + { + sf.Add(new() + { + ID = v.ID, + Size = v.Size, + Name = v.Name, + Encoder = v.Encoder, + NameEncoder = v.NameEncoder, + Key = v.Key, + NameKey = v.NameKey, + Server = Server + }); + ff.Add(v.ID); + } + mmmm.Add(new() + { + Server = Server, + ID = i.ID, + ChannelID = ID, + AuthorID = i.AuthorID, + Context = i.Context, + TimeStamp = i.Timestamp, + EncryptionKey = i.EncryptionKey, + EncoderType = i.EncoderType, + FileIDs = ff.ToArray(), + _Files = sf, + ProfileID = i.ProfileID + }); + } + return await Task.FromResult(mmmm.AsReadOnly()); + } + else + { + throw data?.Error switch + { + ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"), + ErrorCode.ServerError => new Exception($"Error from server: {data.ErrorMessage}"), + ErrorCode.InvalidHeader => new Exception(data.ErrorMessage), + ErrorCode.MissingHeader => new Exception("The header sent to the server was not found. This may be because you app is couropt or you are using the wron API version"), + ErrorCode.Forbidden => new Exception("You are not allowed to do this request"), + _ => new Exception(data?.Error.ToString()), + }; + } + } + } + catch (Exception) + { + throw; + } + } + + public async Task SendMessage(string msg, SocketMessage? ReplyTo = null, + ServerProfile? Profile = null, params string[] files) + { + return await Server.SendMessage(this, msg, ReplyTo, Profile, files); + } + + public async Task SendMessage(string msg, SocketMessage? ReplyTo = null, + ServerProfile? Profile = null, params SocketFile[] files) + { + return await Server.SendMessage(this, msg, ReplyTo, Profile, files); + } + + + + public async Task GetPicture(CancellationToken CancellationToken) + { + bool isc = System.IO.File.Exists(Server.Storage.GetStorageDirectory(StorageDirectory.ChannelIcons) + ID.ToString()); + if (!isc) await Server.GetFromServer($"SocketChannel/GetPicture", Server.Storage.GetStorageDirectory(StorageDirectory.ChannelIcons) + ID.ToString(), CancellationToken, + new KeyValuePair("id", ID.ToString())); + return Server.Storage.GetResourceStream(StorageDirectory.ChannelIcons, ID.ToString()); + } + + public async Task GetParent() + { + if (CategoryID != -1 && RawParent is null) + { + RawParent = await Server.GetCategory(CategoryID, CancellationToken.None); + } + + return RawParent!; + } + public ChannelType Type { get; internal set; } + public DateTime Epoch { get; internal set; } + public string Name { get; internal set; } + public string Description { get; internal set; } + public Task GetRoleOverrides() + { + if (RawRoleOverides is null) + { + RawRoleOverides = new(); + foreach (var ro in RoleOverrides) + { + RawRoleOverides.Add(new() + { + Server = this.Server, + ParentRoleID = ro.RoleID, + GoodPermissions = ro.GoodPermissions, + BadPermissions = ro.BadPermissions + }); + } + } + + return Task.FromResult(RawRoleOverides!.ToArray()); + } + public Task GetUserOverride() + { + if (RawUserOverides is null) + { + RawUserOverides = new(); + } + + return Task.FromResult(RawUserOverides!.ToArray()); + } + + public async Task GetMembers() + { + ServerPermission req = ServerPermission.ViewThis; + List GoodMembers = new(); + List GoodRoles = new(); + List BadRoles = new(); + List GoodPeople = new(); + + foreach (UserOverride cro in await GetUserOverride()) + { + if ((cro.GoodPermissions & req) == req) GoodMembers.Add(cro.UserID); + } + + foreach (RoleOverride ro in (await GetRoleOverrides())) + { + if ((ro.GoodPermissions & req) == req) + { + GoodRoles.Add(ro.ParentRoleID); + } + else if ((ro.BadPermissions & req) == req) + { + BadRoles.Add(ro.ParentRoleID); + } + } + + bool bad = false; + + foreach (Role Role in Server.roles.OrderBy(s => s.Index)) + { + if (BadRoles.Contains(Role.ID)) + { + bad = true; + } + + if (bad && GoodRoles.Contains(Role.ID)) + { + bad = false; + } + + if (!bad) + { + foreach (var m in await Role.GetMembers()) + { + var t = GoodPeople.Where(s => s.Id == m.Id); + if (t.Count() == 0) GoodPeople.Add(m); + } + } + } + + foreach (long m in GoodMembers) + { + var t = GoodPeople.Where(s => s.Id == m); + if (t.Count() == 0) GoodPeople.Add(await Server.GetUser(m, CancellationToken.None)); + } + + return GoodPeople.ToArray(); + } + + public long TitleEncryptionKey { get; internal set; } + public long DescriptionEncryptionKey { get; internal set; } + public long[] EncryptionKeys { get; internal set; } + public EncoderType TitleEncoderType { get; internal set; } + public EncoderType DescriptionEncoderType { get; internal set; } + public EncoderType[] EncoderTypes { get; internal set; } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/SocketFile.cs b/Luski.net/Structures/Public/SocketFile.cs new file mode 100644 index 0000000..8651e77 --- /dev/null +++ b/Luski.net/Structures/Public/SocketFile.cs @@ -0,0 +1,28 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption.Enums; +using Luski.net.Enums; +using Luski.Shared.PublicServers.V1.Enums; + +namespace Luski.net.Structures.Public; + +public class SocketFile +{ + public required PublicServer Server { get; init; } + public required long ID { get; init; } + public required string Name { get; init; } + public required EncoderType Encoder { get; init; } + public required EncoderType NameEncoder { get; init; } + public required long Key { get; init; } + public required long NameKey { get; init; } + public required long Size { get; init; } + + public async Task GetCache(CancellationToken CancellationToken) + { + string d = Server.Storage.GetStorageDirectory(StorageDirectory.Files) + ID.ToString(); + bool isc = System.IO.File.Exists(d); + if (!isc) await Server.GetFromServer($"socketfile?id={ID}", d, CancellationToken); + return Server.Storage.GetResourceStream(StorageDirectory.Files, ID.ToString()); + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/SocketMessage.cs b/Luski.net/Structures/Public/SocketMessage.cs new file mode 100644 index 0000000..ac10319 --- /dev/null +++ b/Luski.net/Structures/Public/SocketMessage.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using JacobTechEncryption.Enums; +using Luski.net.Interfaces; + +namespace Luski.net.Structures.Public; + +public class SocketMessage +{ + public required PublicServer Server { get; init; } = default!; + public required long ID { get; init; } + public required long TimeStamp { get; init; } + public required long ChannelID { get; init; } + public required long AuthorID { get; init; } + public required long ProfileID { get; init; } + public string Context { get; internal set; } + public required long EncryptionKey { get; init; } + public long[] FileIDs { get; internal set; } + public EncoderType EncoderType { get; internal set; } + private SocketChannel? RawParent; + private IUser? au; + private ServerProfile? ap; + internal List _Files { get; set; } = new(); + + public IReadOnlyList Files + { + get => _Files.AsReadOnly(); + } + + public async Task GetParent(CancellationToken token) + { + if (RawParent is null) + { + RawParent = await Server.GetChannel(ChannelID, token); + } + + return RawParent; + } + + public async Task GetAuthor(CancellationToken token) + { + if (au is null) + { + if (AuthorID == Server.User.Id) au = Server.User; + else au = await Server.GetUser(AuthorID, token); + } + + return au; + } + + public async Task GetProfile(CancellationToken token) + { + if (ap is null) + { + ap = await Server.GetProfile(ProfileID, token); + + } + + return ap; + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/SocketUser.cs b/Luski.net/Structures/Public/SocketUser.cs new file mode 100644 index 0000000..4e705d8 --- /dev/null +++ b/Luski.net/Structures/Public/SocketUser.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Luski.net.Classes; +using Luski.net.Enums; +using Luski.net.Interfaces; +using Luski.net.JsonTypes; +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.ServerToClient.HTTP; +using Luski.Shared.PublicServers.V1.Shared; + +namespace Luski.net.Structures.Public; + +public class SocketUser : IUser +{ + public PublicServer Server { get; init; } = default!; + public long Id { get; init; } = default!; + public virtual UserStatus Status { get; internal set; } = default!; + public long ServerProfile { get; init; } + public long[] RoleIds { get; init; } = default!; + private List? RawRoles = null; + + public async Task GetRoles() + { + if (RawRoles is null) + { + RawRoles = new(); + foreach (long r in RoleIds) + { + Role f = await Server.GetRole(r); + RawRoles.Add(f); + } + RawRoles.Sort(new RoleComparer()); + } + + return RawRoles.ToArray(); + } + + public async Task GetAvatar(CancellationToken CancellationToken) + { + bool isc = System.IO.File.Exists(Server.Storage.GetStorageDirectory(StorageDirectory.Avatars) + Id.ToString()); + if (!isc) await Server.GetFromServer($"socketuserprofile/Avatar/{Id}", Server.Storage.GetStorageDirectory(StorageDirectory.Avatars) + Id.ToString(), CancellationToken); + return Server.Storage.GetResourceStream(StorageDirectory.Avatars, Id.ToString()); + } + + public async Task HasPermissions(ServerPermission RequiredPerms) + { + if (Server.OwnerID == Id) return true; + Role[] UserRoleIDList = await GetRoles(); + ServerPermission op = ServerPermission.None; + foreach (Role RoleID in UserRoleIDList) + { + op |= RoleID.ServerPermissions; + } + + return op.HasPermission(RequiredPerms); + } + + public Task GetUserKeys(CancellationToken CancellationToken) + { + KeysGetSTC data = Server.GetFromServer($"Keys/UserKeys/{Id}", KeysGetSTCContext.Default.KeysGetSTC, CancellationToken).Result; + List pki = new(); + foreach (KeyGetSTC key in data.Keys) + { + pki.Add(new() + { + Id = key.ID, + Owner = key.Owner, + EncryptionType = key.EncryptionType, + Data = Convert.FromBase64String(key.Data) + }); + } + return Task.FromResult(pki.ToArray()); + } +} \ No newline at end of file diff --git a/Luski.net/Structures/Public/UserOverride.cs b/Luski.net/Structures/Public/UserOverride.cs new file mode 100644 index 0000000..e7e7955 --- /dev/null +++ b/Luski.net/Structures/Public/UserOverride.cs @@ -0,0 +1,14 @@ +using Luski.Shared.PublicServers.V1.Enums; +using Luski.Shared.PublicServers.V1.Shared; + +namespace Luski.net.Structures.Public; + +public class UserOverride +{ + public required long ID { get; init; } = default!; + public required long UserID { get; init; } = default!; + + public ServerPermission GoodPermissions { get; set; } + + public ServerPermission BadPermissions { get; set; } +} \ No newline at end of file diff --git a/Luski.net/Structures/PublicKeyInfo.cs b/Luski.net/Structures/PublicKeyInfo.cs new file mode 100644 index 0000000..e680be8 --- /dev/null +++ b/Luski.net/Structures/PublicKeyInfo.cs @@ -0,0 +1,11 @@ +using JacobTechEncryption.Enums; + +namespace Luski.net.Structures; + +public class PublicKeyInfo +{ + public long Id { get; init; } + public byte[] Data { get; init; } + public long Owner { get; init; } + public EncryptionType EncryptionType { get; init; } +} \ No newline at end of file