dev #1

Merged
JacobTech merged 14 commits from dev into stable 2024-11-18 23:39:55 -05:00
64 changed files with 5193 additions and 0 deletions
Showing only changes of commit 2eb1abe526 - Show all commits

31
Luski.net.sln Executable file
View File

@ -0,0 +1,31 @@

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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luski.net.tests", "Luski.net.tests\Luski.net.tests.csproj", "{FCA149C8-379B-454A-962A-856F30965C4E}"
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
{FCA149C8-379B-454A-962A-856F30965C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCA149C8-379B-454A-962A-856F30965C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCA149C8-379B-454A-962A-856F30965C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCA149C8-379B-454A-962A-856F30965C4E}.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

515
Luski.net/Encryption.cs Executable file
View File

@ -0,0 +1,515 @@
using Luski.net.Enums;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Luski.net
{
public static class Encryption
{
internal static string? MyPublicKey;
internal static readonly UnicodeEncoding Encoder = new();
private static string? myPrivateKey;
internal static bool Generating = false;
internal static bool Generated = false;
private static string? _serverpublickey = null;
internal static string? ofkey = null;
internal static string? outofkey = null;
internal static string pw = "";
public static int NewKeySize = 4096;
internal static string ServerPublicKey
{
get
{
if (_serverpublickey is null) _serverpublickey = new HttpClient().GetAsync($"https://{Server.InternalDomain}/{Server.API_Ver}/Keys/PublicKey").Result.Content.ReadAsStringAsync().Result;
return _serverpublickey;
}
}
public static void GenerateKeys()
{
if (!Generating)
{
Generating = true;
GenerateNewKeys(out MyPublicKey, out myPrivateKey);
GenerateNewKeys(out outofkey, out ofkey);
Generated = true;
}
}
internal static void GenerateNewKeys(out string Public, out string Private)
{
using RSACryptoServiceProvider r = new(NewKeySize);
Private = r.ToXmlString(true);
Public = r.ToXmlString(false);
return;
}
public static class File
{
internal static void SetOfflineKey(string key)
{
MakeFile(Server.GetKeyFilePath, pw);
LuskiDataFile? fileLayout = JsonSerializer.Deserialize<LuskiDataFile>(FileString(Server.GetKeyFilePath, pw));
fileLayout.OfflineKey = key;
fileLayout.Save(Server.GetKeyFilePath, pw);
}
internal static string? GetOfflineKey()
{
MakeFile(Server.GetKeyFilePath, pw);
LuskiDataFile? fileLayout = JsonSerializer.Deserialize<LuskiDataFile>(FileString(Server.GetKeyFilePath, pw));
return fileLayout?.OfflineKey;
}
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
{
public static string GetKey(long channel)
{
LuskiDataFile? fileLayout;
IEnumerable<ChannelLayout>? 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<LuskiDataFile>(FileString(Server.GetKeyFilePath, pw));
lis = fileLayout?.channels?.Where(s => s.id == channel);
if (lis?.Count() > 0)
{
return lis.First().key;
}
foreach (Branch b in (Branch[])Enum.GetValues(typeof(Branch)))
{
if (b != Server.Branch)
{
try
{
string temp = GetKeyBranch(channel, b);
if (temp is not null)
{
AddKey(channel, temp);
return temp;
}
}
catch
{
}
}
}
throw new Exception("You dont have a key for that channel");
}
finally
{
fileLayout = null;
lis = null;
}
}
internal static string GetKeyBranch(long channel, Branch branch)
{
LuskiDataFile? fileLayout;
IEnumerable<ChannelLayout>? 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<LuskiDataFile>(FileString(Server.GetKeyFilePathBr(branch.ToString()), 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<LuskiDataFile>(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<LuskiDataFile>("{\"channels\":[]}");
l?.Save(dir, password);
}
}
public class LuskiDataFile
{
public static LuskiDataFile GetDataFile(string path, string password)
{
MakeFile(path, password);
return JsonSerializer.Deserialize<LuskiDataFile>(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<ChannelLayout>? 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();
}
}
}
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();
}
}
internal const int PasswordVersion = 0;
internal static byte[] LocalPasswordEncrypt(byte[] Password, int PasswordVersion = PasswordVersion)
{
return PasswordVersion switch
{
0 => SHA256.Create().ComputeHash(Password),
_ => throw new ArgumentException("The value provided was not accepted", nameof(PasswordVersion)),
};
}
internal static string RemotePasswordEncrypt(byte[] Password, int PasswordVersion = PasswordVersion)
{
return PasswordVersion switch
{
0 => Convert.ToBase64String(Encrypt(LocalPasswordEncrypt(Password, PasswordVersion))),
_ => throw new ArgumentException("The value provided was not accepted", nameof(PasswordVersion)),
};
}
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 / 8;
double x = data.Length / (double)size;
int bbb = int.Parse(x.ToString().Split('.')[0]);
if (x.ToString().Contains('.')) bbb++;
byte[]? datasplitout = Array.Empty<byte>();
if (multithread)
{
byte[][]? decccc = Array.Empty<byte[]>();
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(), false);
});
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<byte>();
if (multithread)
{
byte[][]? decccc = Array.Empty<byte[]>();
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(), false);
});
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;
}
}
}

8
Luski.net/Enums/Branch.cs Executable file
View File

@ -0,0 +1,8 @@
namespace Luski.net.Enums;
public enum Branch : short
{
Dev,
Beta,
Master,
}

7
Luski.net/Enums/ChannelType.cs Executable file
View File

@ -0,0 +1,7 @@
namespace Luski.net.Enums;
public enum ChannelType : short
{
DM,
GROUP,
}

18
Luski.net/Enums/DataType.cs Executable file
View File

@ -0,0 +1,18 @@
namespace Luski.net.Enums;
internal 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
}

14
Luski.net/Enums/ErrorCode.cs Executable file
View File

@ -0,0 +1,14 @@
namespace Luski.net.Enums;
public enum ErrorCode
{
MissingToken,
InvalidToken,
MissingPostData,
InvalidPostData,
Forbidden,
ServerError,
MissingHeader,
InvalidHeader,
InvalidURL
}

View File

@ -0,0 +1,9 @@
namespace Luski.net.Enums;
public enum FriendStatus
{
NotFriends,
Friends,
PendingOut,
PendingIn
}

13
Luski.net/Enums/PictureType.cs Executable file
View File

@ -0,0 +1,13 @@
namespace Luski.net.Enums;
public enum PictureType : short
{
png,
jpeg,
bmp,
gif,
ico,
svg,
tif,
webp
}

11
Luski.net/Enums/UserFlag.cs Executable file
View File

@ -0,0 +1,11 @@
using System;
namespace Luski.net.Enums;
[Flags]
public enum UserFlag : short
{
Dev = 0b_001,
Early = 0b_010,
Tester = 0b_100
}

10
Luski.net/Enums/UserStatus.cs Executable file
View File

@ -0,0 +1,10 @@
namespace Luski.net.Enums;
public enum UserStatus : short
{
Offline,
Online,
Idle,
DoNotDisturb,
Invisible
}

30
Luski.net/Exceptions.cs Executable file
View File

@ -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; }
}
}
}

View File

@ -0,0 +1,48 @@
using Luski.net.Sound;
using System;
using System.Threading.Tasks;
using static Luski.net.Exceptions;
namespace Luski.net.Interfaces;
public interface IAudioClient
{
/// <summary>
/// the event is fired when your <see cref="IAudioClient"/> has joined the call
/// </summary>
event Func<Task> Connected;
/// <summary>
/// Tells you if you are muted
/// </summary>
bool Muted { get; }
/// <summary>
/// Tells you if you are deafned
/// </summary>
bool Deafened { get; }
/// <summary>
/// Toggles if you are speaking to your friends
/// </summary>
void ToggleMic();
/// <summary>
/// Toggles if you can hear audio
/// </summary>
void ToggleAudio();
/// <summary>
/// Changes what <see cref="RecordingDevice"/> the call gets its data from
/// </summary>
/// <param name="Device">This is the <see cref="RecordingDevice"/> you want to recored from</param>
/// <exception cref="NotConnectedException"></exception>
void RecordSoundFrom(RecordingDevice Device);
/// <summary>
/// Changes what <see cref="PlaybackDevice"/> the call gets its data from
/// </summary>
/// <param name="Device">This is the <see cref="PlaybackDevice"/> you want to heare outhers</param>
/// <exception cref="NotConnectedException"></exception>
void PlaySoundTo(PlaybackDevice Device);
/// <summary>
/// Joins the Voice call
/// </summary>
/// <exception cref="MissingEventException"></exception>
void JoinCall();
void LeaveCall();
}

View File

@ -0,0 +1,32 @@
using Luski.net.Enums;
using Luski.net.JsonTypes;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Luski.net.Interfaces;
/// <summary>
/// <see cref="ITextChannel"/> contains a list of variables that all channels from luski have
/// </summary>
public interface ITextChannel
{
long Id { get; }
string Title { get; }
string Description { get; }
string Key { get; }
/// <summary>
/// <see cref="ITextChannel.Type"/> returns the current <see cref="ChannelType"/> of the <see cref="ITextChannel"/>
/// </summary>
ChannelType Type { get; }
/// <summary>
/// Sends a <paramref name="Message"/> to the server for the currently selected <see cref="ITextChannel"/>
/// </summary>
/// <param name="Message">The messate you want to send to the server</param>
Task<Task> SendMessage(string Message, params File[] Files);
Task<Task> SendKeysToUsers();
Task<SocketMessage> GetMessage(long ID);
Task<IReadOnlyList<SocketMessage>> GetMessages(long Message_Id, int count = 50);
Task<IReadOnlyList<SocketMessage>> GetMessages(int count = 50);
Task<byte[]> GetPicture();
IReadOnlyList<IUser> Members { get; }
}

45
Luski.net/Interfaces/IUser.cs Executable file
View File

@ -0,0 +1,45 @@
using Luski.net.Enums;
using System.Threading.Tasks;
namespace Luski.net.Interfaces;
/// <summary>
/// Represents the curent user
/// </summary>
public interface IUser
{
/// <summary>
/// The current Id of the user
/// </summary>
long Id { get; }
/// <summary>
/// The cerrent username of the user
/// </summary>
string Username { get; }
/// <summary>
/// The current tag for the user
/// <para>Ex: #1234</para>
/// </summary>
//short Tag { get; }
/// <summary>
/// The current status of the user
/// </summary>
UserStatus Status { get; }
/// <summary>
/// will returen the picture type of the user
/// </summary>
PictureType PictureType { get; }
/// <summary>
/// the current flags of a user
/// </summary>
UserFlag Flags { get; }
/// <summary>
/// Gets the current avatar of the user
/// </summary>
Task<byte[]> GetAvatar();
/// <summary>
/// Gets the current user key
/// </summary>
/// <returns></returns>
Task<string> GetUserKey();
}

28
Luski.net/JsonRequest.cs Executable file
View File

@ -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()}}}";
}
}
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using Luski.net.Enums;
namespace Luski.net.JsonTypes.BaseTypes;
internal class HTTPRequest
{
[JsonPropertyName("data_type")]
[JsonInclude]
public DataType Type { get; set; } = default!;
}

View File

@ -0,0 +1,32 @@
using Luski.net.Enums;
using System.ComponentModel;
using System.Net;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.BaseTypes;
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public class IncomingHTTP
{
[JsonPropertyName("error")]
[JsonInclude]
public ErrorCode? Error { get; internal set; } = default!;
#pragma warning disable SYSLIB1037 // Deserialization of init-only properties is currently not supported in source generation mode.
[JsonIgnore]
public HttpStatusCode StatusCode { get; init; }
#pragma warning restore SYSLIB1037 // Deserialization of init-only properties is currently not supported in source generation mode.
[JsonPropertyName("error_message")]
[JsonInclude]
public string? ErrorMessage { get; internal set; } = default!;
}
[JsonSerializable(typeof(IncomingHTTP))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class IncomingHTTPContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,24 @@
using Luski.net.Enums;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.BaseTypes;
internal class IncomingWSS
{
[JsonPropertyName("type")]
[JsonInclude]
public DataType? Type { get; set; } = default!;
[JsonPropertyName("error")]
[JsonInclude]
public string Error { get; set; } = default!;
}
[JsonSerializable(typeof(IncomingWSS))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified,
WriteIndented = false)]
internal partial class IncomingWSSContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,118 @@
using Luski.net.Enums;
using Luski.net.Interfaces;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Threading.Tasks;
namespace Luski.net.JsonTypes.BaseTypes;
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class SocketUserBase : IncomingHTTP, IUser
{
[JsonPropertyName("id")]
[JsonInclude]
public long Id { get; internal set; } = default!;
[JsonPropertyName("username")]
[JsonInclude]
public string Username { get; internal set; } = default!;
// [JsonPropertyName("tag")]
//[JsonInclude]
//public short Tag { 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!;
[JsonPropertyName("flags")]
[JsonInclude]
public UserFlag Flags { get; internal set; } = default!;
public async Task<byte[]> GetAvatar()
{
if (Server.Cache != null)
{
bool isc = System.IO.File.Exists($"{Server.Cache}/avatars/{Id}");
if (!isc) await Server.GetFromServer($"socketuserprofile/Avatar/{Id}", $"{Server.Cache}/avatars/{Id}");
}
return System.IO.File.ReadAllBytes($"{Server.Cache}/avatars/{Id}");
}
public Task<string> GetUserKey()
{
if (Server._user is null) throw new Exception("you are not loged in");
if (Id == Server._user.Id && Encryption.MyPublicKey is not null) return Task.FromResult(Encryption.MyPublicKey);
string data = Server.GetFromServer($"Keys/GetUserKey/{Id}").Content.ReadAsStringAsync().Result;
return Task.FromResult(data);
}
internal static async Task<TUser> GetUser<TUser>(long UserId, JsonTypeInfo<TUser> Json) where TUser : SocketUserBase, new()
{
TUser user;
if (Server.poeople is null) Server.poeople = new();
if (Server.poeople.Count > 0 && Server.poeople.Any(s => s.Id == UserId))
{
TUser temp = Server.poeople.Where(s => s is TUser && s.Id == UserId).Cast<TUser>().FirstOrDefault()!;
if (temp is SocketRemoteUser && (temp as SocketRemoteUser)!.Channel == null)
{
foreach (SocketDMChannel chan in Server.chans.Where(s => s is SocketDMChannel).Cast<SocketDMChannel>())
{
if (chan.Type == ChannelType.DM && chan.Id != 0 && chan.MemberIdList is not null)
{
if (chan.MemberIdList.Any(s => s == UserId)) (temp as SocketRemoteUser)!.Channel = chan;
}
}
}
return temp!;
}
while (true)
{
if (Server.CanRequest)
{
user = await Server.GetFromServer("socketuser",
Json,
new System.Collections.Generic.KeyValuePair<string, string?>("id", UserId.ToString()));
break;
}
}
if (user is null) throw new Exception("Server did not return a user");
if (Server.poeople.Count > 0 && Server.poeople.Any(s => s.Id == UserId))
{
foreach (IUser? p in Server.poeople.Where(s => s.Id == UserId))
{
Server.poeople.Remove(p);
}
}
if (Server._user is not null && UserId != 0 && UserId != Server._user.Id)
{
foreach (SocketDMChannel chan in Server.chans.Where(s => s is SocketDMChannel).Cast<SocketDMChannel>())
{
if (chan.Id != 0 && chan.MemberIdList is not null)
{
if (chan.MemberIdList.Any(s => s == UserId)) (user as SocketRemoteUser)!.Channel = chan;
}
}
}
Server.poeople.Add(user);
return user;
}
}
[JsonSerializable(typeof(SocketUserBase))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never)]
internal partial class SocketUserBaseContext : JsonSerializerContext
{
}

107
Luski.net/JsonTypes/File.cs Executable file
View File

@ -0,0 +1,107 @@
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.Tasks;
namespace Luski.net.JsonTypes;
public class File : IncomingHTTP
{
[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 void DownloadBytes(string Loc, long key)
{
//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);
string Key = (key == 0 ? Encryption.MyPublicKey : Encryption.File.Channels.GetKey(key))!;
Encryption.AES.Decrypt(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;
}
internal async Task<long> Upload(string keyy)
{
if (Name != null) Name = Convert.ToBase64String(Encryption.Encrypt(Name, keyy));
Debug.WriteLine("uploading");
string NPath = Encryption.AES.Encrypt(loc, keyy);
File sf = await Server.SendServer(
"SocketMessage/UploadFile",
NPath,
FileContext.Default.File,
new KeyValuePair<string, string?>("name", Name));
try { System.IO.File.Delete(NPath); } catch { }
Debug.WriteLine("done uploading");
if (sf.Error is not null) throw new Exception(sf.ErrorMessage);
return sf.Id;
}
internal void decrypt()
{
if (Name is not null) Name = Encryption.Encoder.GetString(Encryption.Decrypt(Convert.FromBase64String(Name), key));
}
}
[JsonSerializable(typeof(File))]
internal partial class FileContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,27 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
internal class FriendRequestResult : IncomingHTTP
{
[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
{
}

View File

@ -0,0 +1,21 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.HTTP;
internal class Channel : HTTPRequest
{
[JsonPropertyName("id")]
[JsonInclude]
public long Id { get; set; } = default!;
}
[JsonSerializable(typeof(Channel))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class ChannelContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,30 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.HTTP;
internal class FriendRequest : HTTPRequest
{
[JsonPropertyName("id")]
[JsonInclude]
public long Id { get; set; } = default!;
[JsonPropertyName("subtype")]
[JsonInclude]
public int SubType { get; set; } = default!;
[JsonPropertyName("username")]
[JsonInclude]
public string Username { get; set; } = default!;
[JsonPropertyName("tag")]
[JsonInclude]
public short Tag { get; set; } = default!;
}
[JsonSerializable(typeof(FriendRequest))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class FriendRequestContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,24 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.HTTP;
internal class FriendRequestResultOut : HTTPRequest
{
[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
{
}

View File

@ -0,0 +1,27 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.HTTP;
internal class Message : HTTPRequest
{
[JsonPropertyName("channel_id")]
[JsonInclude]
public long Channel { get; set; } = default!;
[JsonPropertyName("content")]
[JsonInclude]
public string Context { get; set; } = default!;
[JsonPropertyName("files")]
[JsonInclude]
public long[] Files { get; set; } = default!;
}
[JsonSerializable(typeof(Message))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class MessageContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
using Luski.net.Enums;
using Luski.net.JsonTypes.BaseTypes;
namespace Luski.net.JsonTypes.HTTP;
internal class Status : HTTPRequest
{
[JsonPropertyName("status")]
[JsonInclude]
public UserStatus UserStatus { get; set; } = default!;
}
[JsonSerializable(typeof(Status))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class StatusContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,10 @@
namespace Luski.net.JsonTypes
{
internal class KeyExchange
{
public long channel { get; set; } = default!;
public string key { get; set; } = default!;
public long? to { get; set; } = default!;
}
}

20
Luski.net/JsonTypes/Login.cs Executable file
View File

@ -0,0 +1,20 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
internal class Login : IncomingHTTP
{
[JsonPropertyName("login_token")]
public string? Token { get; set; } = default!;
}
[JsonSerializable(typeof(Login))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class LoginContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,19 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
internal class OfflineKeyData : IncomingHTTP
{
public KeyExchange[]? keys { get; internal set; } = default!;
}
[JsonSerializable(typeof(OfflineKeyData))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class OfflineKeyDataContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,178 @@
using Luski.net.Interfaces;
using Luski.net.JsonTypes.BaseTypes;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
public class SocketAppUser : SocketUserBase
{
[JsonPropertyName("email")]
[JsonInclude]
public string Email { get; internal set; } = default!;
[JsonIgnore]
public IReadOnlyList<SocketChannel> Channels
{
get
{
if (_Channels is null || ChannelIdList is not null)
{
if (ChannelIdList.Length != 0)
{
_Channels = new List<SocketChannel>();
foreach (long channel in ChannelIdList)
{
SocketChannel s = SocketChannel.GetChannel(channel, SocketChannelContext.Default.SocketChannel).Result;
Server.chans.Remove(s);
switch (s.Type)
{
case Enums.ChannelType.GROUP:
_Channels.Add(SocketChannel.GetChannel(channel, SocketGroupChannelContext.Default.SocketGroupChannel).Result);
break;
case Enums.ChannelType.DM:
_Channels.Add(SocketChannel.GetChannel(channel, SocketDMChannelContext.Default.SocketDMChannel).Result);
break;
}
}
}
else _Channels = new List<SocketChannel>();
}
return _Channels.AsReadOnly();
}
}
[JsonIgnore]
public IReadOnlyList<SocketRemoteUser> 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;
SocketRemoteUser frq = GetUser(id, SocketRemoteUserContext.Default.SocketRemoteUser).Result;
_FriendRequests.Add(frq);
}
}
else _FriendRequests = new();
}
return _FriendRequests.AsReadOnly();
}
}
[JsonIgnore]
public IReadOnlyList<SocketRemoteUser> Friends
{
get
{
if (_Friends is null || FriendIdList is not null)
{
if (ChannelIdList.Length != 0)
{
_Friends = new List<SocketRemoteUser>();
foreach (long person in FriendIdList)
{
_Friends.Add(GetUser(person, SocketRemoteUserContext.Default.SocketRemoteUser).Result);
}
}
else _Friends = new List<SocketRemoteUser>();
}
return _Friends.AsReadOnly();
}
}
[JsonPropertyName("selected_channel")]
[JsonInclude]
public long SelectedChannel { get; internal set; } = default!;
[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<SocketChannel> _Channels = default!;
[JsonIgnore]
private List<SocketRemoteUser> _Friends = default!;
[JsonIgnore]
private List<SocketRemoteUser> _FriendRequests = default!;
public class FR
{
public long from { get; set; } = default!;
public long user_id { get; set; } = default!;
}
internal void AddFriend(SocketRemoteUser User)
{
if (Server.poeople.Any(s => s.Id == User.Id))
{
IEnumerable<IUser> 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(SocketRemoteUser User)
{
if (Server.poeople.Any(s => s.Id == User.Id))
{
IEnumerable<IUser> b = Server.poeople.Where(s => s.Id == User.Id);
foreach (IUser item in b)
{
Server.poeople.Remove(item);
}
}
Server.poeople.Add(User);
foreach (SocketRemoteUser user in _FriendRequests)
{
if (User.Id == user.Id)
{
_FriendRequests.Remove(User);
}
}
}
internal void AddFriendRequest(SocketRemoteUser User)
{
if (Server.poeople.Any(s => s.Id == User.Id))
{
IEnumerable<IUser> 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(SocketAppUser))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never)]
internal partial class SocketAppUserContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,23 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
internal class SocketBulkMessage : IncomingHTTP
{
[JsonPropertyName("messages")]
[JsonInclude]
public SocketMessage[]? Messages { get; set; } = default!;
}
[JsonSerializable(typeof(SocketBulkMessage))]
[JsonSerializable(typeof(SocketAppUser))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never)]
internal partial class SocketBulkMessageContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,173 @@
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;
namespace Luski.net.JsonTypes;
public class SocketChannel : IncomingHTTP
{
[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!;
[JsonPropertyName("members")]
[JsonInclude]
public long[] MemberIdList { get; internal set; } = default!;
[JsonIgnore]
public IReadOnlyList<IUser> Members
{
get
{
if (MemberIdList is null || MemberIdList.Length == 0) return Array.Empty<IUser>().ToList().AsReadOnly();
if (_members is null || !_members.Any())
{
_members = new();
foreach (long member in MemberIdList)
{
if (member != Server._user!.Id) _members.Add(SocketUserBase.GetUser(member, SocketRemoteUserContext.Default.SocketRemoteUser).Result);
else _members.Add(Server._user);
}
}
return _members.AsReadOnly();
}
}
[JsonIgnore]
private List<IUser> _members = new();
public async Task<Task> SendKeysToUsers()
{
if (Key is null)
{
StartKeyProcessAsync().Wait();
return Task.CompletedTask;
}
int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * Server.Percent) * 2.0));
if (num == 0) num = 1;
string? lkey = Encryption.File.Channels.GetKey(Id);
Parallel.ForEach(Members, new ParallelOptions()
{
MaxDegreeOfParallelism = num
}, async i =>
{
if (i.Id != Server._user?.Id)
{
string key = await i.GetUserKey();
if (!string.IsNullOrEmpty(key))
{
WSSKeyExchange send = new()
{
to = i.Id,
channel = Id,
key = Convert.ToBase64String(Encryption.Encrypt(lkey, key))
};
Server.SendServer(send, WSSKeyExchangeContext.Default.WSSKeyExchange);
}
}
});
return Task.CompletedTask;
}
internal async Task StartKeyProcessAsync()
{
Encryption.GenerateNewKeys(out string Public, out string Private);
Key = Public;
HttpResponseMessage b;
using (HttpClient web = new())
{
web.DefaultRequestHeaders.Add("token", Server.Token);
b = web.PostAsync($"https://{Server.InternalDomain}/{Server.API_Ver}/SocketChannel/SetKey/{Id}", new StringContent(Key)).Result;
}
int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 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 != Server._user?.Id)
{
string key = i.GetUserKey().Result;
if (!string.IsNullOrEmpty(key))
{
WSSKeyExchange send = new()
{
to = i.Id,
channel = Id,
key = Convert.ToBase64String(Encryption.Encrypt(Private, key))
};
Server.SendServer(send, WSSKeyExchangeContext.Default.WSSKeyExchange);
}
}
});
}
internal static async Task<TChannel> GetChannel<TChannel>(long id, JsonTypeInfo<TChannel> Json) where TChannel : SocketChannel, new()
{
TChannel request;
if (Server.chans is null) Server.chans = new();
if (Server.chans.Count > 0 && Server.chans.Any(s => s.Id == id))
{
return Server.chans.Where(s => s is TChannel && s.Id == id).Cast<TChannel>().FirstOrDefault()!;
}
while (true)
{
if (Server.CanRequest)
{
request = await Server.GetFromServer($"SocketChannel/Get/{id}", Json);
break;
}
}
if (request is null) throw new Exception("Something was wrong with the server responce");
if (request.Error is null)
{
if (Server.chans is null) Server.chans = new();
if (Server.chans.Count > 0 && Server.chans.Any(s => s.Id == request.Id))
{
foreach (SocketChannel? p in Server.chans.Where(s => s.Id == request.Id))
{
Server.chans.Remove(p);
}
}
Server.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}'"),
};
}
}
[JsonSerializable(typeof(SocketChannel))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class SocketChannelContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,33 @@
using Luski.net.JsonTypes.BaseTypes;
using System.Linq;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
public class SocketDMChannel : SocketTextChannel
{
public SocketRemoteUser User
{
get
{
if (_user is null)
{
var list = MemberIdList.ToList();
list.Remove(Server._user!.Id);
_user = SocketUserBase.GetUser(list.FirstOrDefault(), SocketRemoteUserContext.Default.SocketRemoteUser).Result;
}
return _user;
}
}
public SocketRemoteUser _user = null!;
}
[JsonSerializable(typeof(SocketDMChannel))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class SocketDMChannelContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes;
public class SocketGroupChannel : SocketTextChannel
{
[JsonPropertyName("owner")]
[JsonInclude]
public long Owner { get; internal set; } = default!;
}
[JsonSerializable(typeof(SocketGroupChannel))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class SocketGroupChannelContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,87 @@
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;
namespace Luski.net.JsonTypes;
public class SocketMessage : IncomingHTTP
{
[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<SocketTextChannel> GetChannel()
{
if (Server.chans.Any(s => s.Id == ChannelID))
{
return (SocketTextChannel)Server.chans.Where(s => s.Id == ChannelID).First();
}
else
{
SocketTextChannel ch = await SocketChannel.GetChannel(ChannelID, SocketTextChannelContext.Default.SocketTextChannel);
Server.chans.Add(ch);
return ch;
}
}
public async Task<IUser> GetAuthor()
{
if (Server._user!.Id != AuthorID) return await SocketUserBase.GetUser(AuthorID, SocketRemoteUserContext.Default.SocketRemoteUser);
else return Server._user;
}
internal void decrypt(string? key)
{
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
Context = Encryption.Encoder.GetString(Encryption.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();
}
}
}
internal static async Task<SocketMessage> GetMessage(long id)
{
SocketMessage message;
while (true)
{
if (Server.CanRequest)
{
message = await Server.GetFromServer("socketmessage",
SocketMessageContext.Default.SocketMessage,
new System.Collections.Generic.KeyValuePair<string, string?>("msg_id", id.ToString()));
break;
}
}
if (message is not null) return message;
throw new Exception("Server did not return a message");
}
}
[JsonSerializable(typeof(SocketMessage))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
public partial class SocketMessageContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,35 @@
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;
namespace Luski.net.JsonTypes;
public class SocketRemoteUser : SocketUserBase
{
[JsonPropertyName("friend_status")]
[JsonInclude]
public FriendStatus FriendStatus { get; internal set; } = default!;
[JsonIgnore]
public SocketDMChannel Channel { get; internal set; } = default!;
internal SocketRemoteUser Clone()
{
return (SocketRemoteUser)MemberwiseClone();
}
}
[JsonSerializable(typeof(SocketRemoteUser))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class SocketRemoteUserContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,168 @@
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.Tasks;
namespace Luski.net.JsonTypes;
public class SocketTextChannel : SocketChannel, ITextChannel
{
public async Task<SocketMessage> GetMessage(long ID)
{
return await SocketMessage.GetMessage(ID);
}
public async Task<byte[]> GetPicture()
{
if (Type == ChannelType.DM) return Members.First().GetAvatar().Result;
else
{
if (Server.Cache != null)
{
bool isc = System.IO.File.Exists($"{Server.Cache}/channels/{Id}");
if (!isc) await Server.GetFromServer($"SocketChannel/GetPicture/{Id}", $"{Server.Cache}/channels/{Id}");
}
return System.IO.File.ReadAllBytes($"{Server.Cache}/channels/{Id}");
}
}
public async Task<IReadOnlyList<SocketMessage>> GetMessages(long Message_Id, 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,
new KeyValuePair<string, string?>("channel_id", Id.ToString()),
new KeyValuePair<string, string?>("messages", count.ToString()),
new KeyValuePair<string, string?>("mostrecentid", Message_Id.ToString()));
if (data.Error is null)
{
int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * Server.Percent) * 2.0));
if (num == 0) num = 1;
string? key = Encryption.File.Channels.GetKey(Id);
if (data is null) throw new Exception("Invalid data from server");
if (data.Messages is null) data.Messages = Array.Empty<SocketMessage>();
Parallel.ForEach(data.Messages, new ParallelOptions()
{
MaxDegreeOfParallelism = num
}, i =>
{
i.decrypt(key);
});
key = null;
return await Task.FromResult(data.Messages.ToList().AsReadOnly() as IReadOnlyList<SocketMessage>);
}
else
{
throw new Exception(data.ErrorMessage);
}
}
}
public async Task<IReadOnlyList<SocketMessage>> GetMessages(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
{
SocketBulkMessage data = await Server.GetFromServer("SocketBulkMessage",
SocketBulkMessageContext.Default.SocketBulkMessage,
new KeyValuePair<string, string?>("id", Id.ToString()),
new KeyValuePair<string, string?>("messages", count.ToString()));
if (data is not null && !data.Error.HasValue)
{
int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * Server.Percent) * 2.0));
if (num == 0) num = 1;
string? key = Encryption.File.Channels.GetKey(Id);
if (data.Messages is null) data.Messages = Array.Empty<SocketMessage>();
Parallel.ForEach(data.Messages, new ParallelOptions()
{
MaxDegreeOfParallelism = num
}, i =>
{
i.decrypt(key);
});
key = null;
return await Task.FromResult(data.Messages.ToList().AsReadOnly() as IReadOnlyList<SocketMessage>);
}
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<Task> SendMessage(string Message, params File?[] Files)
{
string key = Encryption.File.Channels.GetKey(Id);
if (Id == 0) key = Encryption.ServerPublicKey;
HTTP.Message m = new()
{
Context = Convert.ToBase64String(Encryption.Encrypt(Message, key)),
Channel = Id,
};
if (Files is not null && Files.Length > 0)
{
List<long> 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));
Files[i] = null;
}
}
m.Files = bb.ToArray();
}
IncomingHTTP data = await Server.SendServer("socketmessage", m, HTTP.MessageContext.Default.Message, IncomingHTTPContext.Default.IncomingHTTP);
if (data.Error is not null && data.ErrorMessage != "Server responded with empty data") throw new Exception(data.ErrorMessage);
return Task.CompletedTask;
}
}
[JsonSerializable(typeof(SocketTextChannel))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Default,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
internal partial class SocketTextChannelContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,17 @@
using Luski.net.Enums;
using System.Text.Json.Serialization;
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!;
}

View File

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

View File

@ -0,0 +1,25 @@
using Luski.net.Enums;
using Luski.net.JsonTypes.BaseTypes;
using System.Text.Json.Serialization;
namespace Luski.net.JsonTypes.WSS;
internal class WSSLogin : IncomingWSS
{
[JsonPropertyName("token")]
[JsonInclude]
public string Token { 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
{
}

33
Luski.net/Luski.net.csproj Executable file
View File

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Title>Luski.net</Title>
<Authors>JacobTech</Authors>
<Company>JacobTech, LLC</Company>
<Description>A wrapper for the luski API</Description>
<PackageProjectUrl>https://www.jacobtech.com/Luski/Documentation</PackageProjectUrl>
<RepositoryUrl>https://github.com/JacobTech-com/Luski.net</RepositoryUrl>
<IncludeSymbols>True</IncludeSymbols>
<FileVersion>1.0.0</FileVersion>
<Version>1.1.4-alpha</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="websocketsharp.core" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Sockets\" />
<Folder Include="Sound\" />
</ItemGroup>
<Target Name="CustomActionsAfterPublish" AfterTargets="Pack">
<Message Text="Actions AfterPublish: $(PackageId).$(PackageVersion).nupkg" Importance="high" />
<Exec Command="nuget push -Source https://nuget.jacobtech.com/v3/index.json bin\Release\$(PackageId).$(PackageVersion).nupkg" />
</Target>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>C:\Users\techn\source\repos\JacobTech-com\Luski.net\Luski.net\Luski.net\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net6.0\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<History>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;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

17
Luski.net/Server.Cleanup.cs Executable file
View File

@ -0,0 +1,17 @@
using System;
using System.IO;
namespace Luski.net;
public sealed partial class Server : IDisposable
{
~Server()
{
try { if (Directory.Exists(Cache)) Directory.Delete(Cache, true); } catch { }
}
public void Dispose()
{
try { if (Directory.Exists(Cache)) Directory.Delete(Cache, true); } catch { }
}
}

129
Luski.net/Server.Constructors.cs Executable file
View File

@ -0,0 +1,129 @@
using Luski.net.Enums;
using Luski.net.JsonTypes;
using Luski.net.JsonTypes.BaseTypes;
using Luski.net.JsonTypes.WSS;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using WebSocketSharp;
using File = System.IO.File;
namespace Luski.net;
public sealed partial class Server
{
internal Server(string Email, string Password, Branch branch = Branch.Master, string? Username = null, string? pfp = null)
{
if (!Encryption.Generating)
{
Encryption.GenerateKeys();
}
while (!Encryption.Generated) { }
InternalDomain = $"api.{branch}.luski.JacobTech.com";
Branch = branch;
login = true;
Login json;
List<KeyValuePair<string, string?>> heads = new()
{
new("key", Encryption.MyPublicKey),
new("email", Convert.ToBase64String(Encryption.Encrypt(Email))),
new("password", Encryption.RemotePasswordEncrypt(Encryption.Encoder.GetBytes(Password)))
};
if (File.Exists("LastPassVer.txt") && int.TryParse(File.ReadAllText("LastPassVer.txt"), out int lpv) && lpv < Encryption.PasswordVersion && lpv >= 0)
{
heads.Add(new("old_password", Encryption.RemotePasswordEncrypt(Encryption.Encoder.GetBytes(Password), lpv)));
heads.Add(new("old_version", lpv.ToString()));
}
if (pfp is not null)
{
heads.Add(new("username", Username));
json = SendServer(
"CreateAccount",
pfp,
LoginContext.Default.Login,
heads.ToArray()).Result;
}
else
{
json = GetFromServer(
"Login",
LoginContext.Default.Login,
heads.ToArray()).Result;
}
if (json.Error is not null) throw new Exception($"Luski appears to be down at the current moment: {json.ErrorMessage}");
if (Encryption.ofkey is null || Encryption.outofkey 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($"wss://{InternalDomain}/WSS/{API_Ver}");
ServerOut.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls13;
ServerOut.OnMessage += DataFromServer;
ServerOut.WaitTime = new TimeSpan(0, 0, 5);
ServerOut.OnError += ServerOut_OnError;
ServerOut.Connect();
string Infermation = $"{{\"token\": \"{json.Token}\"}}";
SendServer(new WSSLogin() { Token = json.Token! }, WSSLoginContext.Default.WSSLogin);
while (Token is null && Error is null)
{
}
if (Error is not null)
{
throw new Exception(Error);
}
if (Token is null) throw new Exception("Server did not send a token");
CanRequest = true;
_user = SocketUserBase.GetUser(long.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(Token.Split('.')[0]))), SocketAppUserContext.Default.SocketAppUser).Result;
if (_user is null || _user.Error is not null) throw new Exception("Something went wrong getting your user infermation");
_ = _user.Channels;
foreach (var ch in chans)
{
_ = ch.Members;
}
_user.Email = Email;
_ = UpdateStatus(UserStatus.Online);
try
{
Encryption.pw = Email.ToLower() + Password;
_ = Encryption.File.GetOfflineKey();
}
catch
{
try
{
Encryption.pw = Email + Password;
var temp222 = Encryption.File.LuskiDataFile.GetDefualtDataFile();
Encryption.pw = Email.ToLower() + Password;
if (temp222 is not null) temp222.Save(GetKeyFilePath, Encryption.pw);
}
catch
{
Token = null;
Error = null;
ServerOut.Close();
throw new Exception("The key file you have is getting the wrong pasword. Type your Email in the same way you creaated your account to fix this error.");
}
}
OfflineKeyData offlinedata = GetFromServer("Keys/GetOfflineData", OfflineKeyDataContext.Default.OfflineKeyData).Result;
if (string.IsNullOrEmpty(Encryption.File.GetOfflineKey())) Encryption.File.SetOfflineKey(Encryption.ofkey);
if (offlinedata is not null && offlinedata.Error is null && offlinedata.keys is not null)
{
foreach (KeyExchange key in offlinedata.keys)
{
Encryption.File.Channels.AddKey(key.channel, Encryption.Encoder.GetString(Encryption.Decrypt(Convert.FromBase64String(key.key), Encryption.File.GetOfflineKey())));
}
}
System.IO.File.WriteAllText("LastPassVer.txt", Encryption.PasswordVersion.ToString());
Encryption.File.SetOfflineKey(Encryption.ofkey);
using HttpClient setkey = new();
setkey.DefaultRequestHeaders.Add("token", Token);
_ = setkey.PostAsync($"https://{InternalDomain}/{API_Ver}/Keys/SetOfflineKey", new StringContent(Encryption.outofkey)).Result;
Encryption.outofkey = null;
Encryption.ofkey = null;
}
else throw new Exception(json?.ErrorMessage);
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using Luski.net.Enums;
using Luski.net.JsonTypes;
using Luski.net.JsonTypes.WSS;
using WebSocketSharp;
namespace Luski.net;
public sealed partial class Server
{
internal Server(string Email, string Password, string Username, byte[] PFP, Branch branch = Branch.Master)
{
Encryption.pw = Email.ToLower() + Password;
if (!Encryption.Generating)
{
Encryption.GenerateKeys();
}
while (!Encryption.Generated) { }
if (Encryption.ofkey is null || Encryption.outofkey is null) throw new Exception("Something went wrong generating the offline keys");
string Result;
InternalDomain = $"api.{branch}.luski.JacobTech.com";
Branch = branch;
using (HttpClient web = new())
{
web.DefaultRequestHeaders.Add("key", Encryption.MyPublicKey);
web.DefaultRequestHeaders.Add("email", Convert.ToBase64String(Encryption.Encrypt(Email)));
web.DefaultRequestHeaders.Add("password", Convert.ToBase64String(Encryption.Encrypt(Password)));
web.DefaultRequestHeaders.Add("username", Username);
HttpResponseMessage? d = web.PostAsync($"https://{InternalDomain}/{API_Ver}/CreateAccount", new StringContent(Convert.ToBase64String(PFP))).Result;
if (d is null || !d.IsSuccessStatusCode) throw new Exception("Luski appears to be down at the current moment");
Result = d.Content.ReadAsStringAsync().Result;
web.DefaultRequestHeaders.Clear();
}
Login? json = JsonSerializer.Deserialize(Result, LoginContext.Default.Login);
if (json is not null && json.Error is null)
{
ServerOut = new WebSocket($"wss://{InternalDomain}/{API_Ver}");
ServerOut.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls13;
ServerOut.OnMessage += DataFromServer;
ServerOut.WaitTime = new TimeSpan(0, 0, 5);
ServerOut.OnError += ServerOut_OnError;
ServerOut.Connect();
string Infermation = $"{{\"token\": \"{json.Token}\"}}";
SendServer(new WSSLogin() { Token = json.Token}, WSSLoginContext.Default.WSSLogin);
while (Token is null && Error is null)
{
}
if (Error is not null)
{
throw new Exception(Error);
}
if (Token is null) throw new Exception("Server did not send a token");
CanRequest = true;
string data;
using (HttpClient web = new())
{
web.DefaultRequestHeaders.Add("token", Token);
web.DefaultRequestHeaders.Add("id", Encoding.UTF8.GetString(Convert.FromBase64String(Token.Split('.')[0])));
data = web.GetAsync($"https://{InternalDomain}/{API_Ver}/SocketUser").Result.Content.ReadAsStringAsync().Result;
}
_user = JsonSerializer.Deserialize<SocketAppUser>(data);
if (_user is null || _user.Error is not null) throw new Exception("Something went wrong getting your user infermation");
_ = _user.Channels;
foreach (var ch in chans)
{
_ = ch.Members;
}
_user.Email = Email;
UpdateStatus(UserStatus.Online);
Encryption.File.SetOfflineKey(Encryption.ofkey);
using HttpClient setkey = new();
setkey.DefaultRequestHeaders.Add("token", Token);
_ = setkey.PostAsync($"https://{InternalDomain}/{API_Ver}/Keys/SetOfflineKey", new StringContent(Encryption.outofkey)).Result;
Encryption.outofkey = null;
Encryption.ofkey = null;
}
else throw new Exception(json?.ErrorMessage);
}
public static Server CreateAccount(string Email, string Password, string Username, byte[] PFP, Branch branch = Branch.Master)
{
return new Server(Email, Password, Username, PFP, branch);
}
public static Server CreateAccount(string Email, string Password, string Username, string PFP, Branch branch = Branch.Master)
{
return new Server(Email, Password, branch, Username, PFP);
}
}

21
Luski.net/Server.Events.cs Executable file
View File

@ -0,0 +1,21 @@
using Luski.net.Interfaces;
using Luski.net.JsonTypes;
using System;
using System.Threading.Tasks;
namespace Luski.net;
public sealed partial class Server
{
public event Func<SocketMessage, Task>? MessageReceived;
public event Func<IUser, IUser, Task>? UserStatusUpdate;
public event Func<SocketRemoteUser, Task>? ReceivedFriendRequest;
public event Func<SocketRemoteUser, bool, Task>? FriendRequestResult;
public event Func<ITextChannel, SocketRemoteUser, Task>? IncommingCall;
public event Func<Exception, Task>? OnError;
}

83
Luski.net/Server.Globals.cs Executable file
View File

@ -0,0 +1,83 @@
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 WebSocketSharp;
namespace Luski.net;
public sealed partial class Server
{
internal static string JT { get { return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/JacobTech"; } }
internal static SocketAudioClient? AudioClient = null;
internal static string? Token = null, Error = null;
internal static bool CanRequest = false;
internal static SocketAppUser? _user;
internal static string InternalDomain = "api.master.luski.jacobtech.com", 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 string Domain { get { return InternalDomain; } }
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 const string API_Ver = "v1";
internal static List<IUser> poeople = new();
internal static List<SocketChannel> 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;
}
}

118
Luski.net/Server.Incoming.cs Executable file
View File

@ -0,0 +1,118 @@
using Luski.net.Enums;
using Luski.net.JsonTypes;
using Luski.net.JsonTypes.BaseTypes;
using Luski.net.JsonTypes.HTTP;
using Luski.net.JsonTypes.WSS;
using System;
using System.Text.Json;
using WebSocketSharp;
namespace Luski.net
{
public sealed partial class Server
{
private void DataFromServer(object? sender, MessageEventArgs e)
{
if (e.IsPing) return;
IncomingWSS? data = JsonSerializer.Deserialize(e.Data, IncomingWSSContext.Default.IncomingWSS);
switch (data?.Type)
{
case DataType.Login:
WSSLogin n = JsonSerializer.Deserialize(e.Data, WSSLoginContext.Default.WSSLogin)!;
Token = n.Token;
break;
case DataType.Error:
if (Token is null)
{
Error = data.Error;
}
else
{
if (OnError is not null)
{
_ = OnError.Invoke(new Exception(data.Error));
}
}
break;
case DataType.Message_Create:
if (MessageReceived is not null)
{
SocketMessage? m = JsonSerializer.Deserialize<SocketMessage>(e.Data);
if (m is not null)
{
m.decrypt(Encryption.File.Channels.GetKey(m.ChannelID));
_ = MessageReceived.Invoke(m);
}
}
break;
case DataType.Status_Update:
if (UserStatusUpdate is not null)
{
StatusUpdate? SU = JsonSerializer.Deserialize<StatusUpdate>(e.Data);
if (SU is not null)
{
SocketRemoteUser after = SocketUserBase.GetUser(SU.id, SocketRemoteUserContext.Default.SocketRemoteUser).Result;
after.Status = SU.after;
SocketRemoteUser before = after.Clone();
before.Status = SU.before;
_ = UserStatusUpdate.Invoke(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(SocketUserBase.GetUser(request.Id, SocketRemoteUserContext.Default.SocketRemoteUser).Result);
}
break;
case DataType.Friend_Request_Result:
if (FriendRequestResult is not null)
{
FriendRequestResult? FRR = JsonSerializer.Deserialize<FriendRequestResult>(e.Data);
if (FRR is not null && FRR.Channel is not null && FRR.Id is not null && FRR.Result is not null)
{
SocketDMChannel chan = SocketChannel.GetChannel((long)FRR.Channel, SocketDMChannelContext.Default.SocketDMChannel).Result;
chans.Add(chan);
SocketRemoteUser from1 = SocketUserBase.GetUser((long)FRR.Id, SocketRemoteUserContext.Default.SocketRemoteUser).Result;
from1.Channel = chan;
_ = FriendRequestResult.Invoke(from1, (bool)FRR.Result);
}
}
break;
case DataType.Call_Info:
if (IncommingCall is not null)
{
callinfoinc? ci = JsonSerializer.Deserialize<callinfoinc>(e.Data);
if (ci is not null) _ = IncommingCall.Invoke(SocketChannel.GetChannel(ci.channel, SocketTextChannelContext.Default.SocketTextChannel).Result, SocketUserBase.GetUser(ci.from, SocketRemoteUserContext.Default.SocketRemoteUser).Result);
}
break;
case DataType.Call_Data:
if (AudioClient is not null)
{
AudioClient.Givedata(e.Data);
}
break;
case DataType.Key_Exchange:
try
{
KeyExchange? KE = JsonSerializer.Deserialize<KeyExchange>(e.Data);
if (KE is not null) Encryption.File.Channels.AddKey(KE.channel, Encryption.Encoder.GetString(Encryption.Decrypt(Convert.FromBase64String(KE.key))));
}
catch (Exception ex)
{
if (OnError is not null) OnError.Invoke(ex);
}
break;
default:
break;
}
}
private class callinfoinc
{
public long channel { get; set; } = default!;
public long from { get; set; } = default!;
}
}
}

12
Luski.net/Server.Login.cs Executable file
View File

@ -0,0 +1,12 @@
using Luski.net.Enums;
using System.Threading.Tasks;
namespace Luski.net;
public sealed partial class Server
{
public static async Task<Server> Login(string Email, string Password, Branch branch = Branch.Master)
{
return new Server(Email, Password, branch);
}
}

298
Luski.net/Server.cs Executable file
View File

@ -0,0 +1,298 @@
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.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading.Tasks;
namespace Luski.net;
public sealed partial class Server
{
#pragma warning disable CA1822 // Mark members as static
/// <summary>
/// Creates an audio client for the <paramref name="channel_id"/> you want to talk on
/// </summary>
/// <param name="ID">The channel <see cref="ITextChannel.ID"/> you want to talk on</param>
/// <returns><seealso cref="IAudioClient"/></returns>
public IAudioClient CreateAudioClient(long channel_id)
{
// if (AudioClient != null) throw new Exception("audio client alread created");
SocketAudioClient client = new(channel_id, OnError);
AudioClient = client;
return client;
}
public async Task<SocketRemoteUser> SendFriendResult(long user, bool answer)
{
FriendRequestResult json = await SendServer("FriendRequestResult",
new FriendRequestResultOut()
{
Id = user,
Result = answer
},
FriendRequestResultOutContext.Default.FriendRequestResultOut,
FriendRequestResultContext.Default.FriendRequestResult);
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);
_ = chan.StartKeyProcessAsync();
chans.Add(chan);
}
else
{
throw new Exception(json?.Error.ToString());
}
return SocketUserBase.GetUser(user, SocketRemoteUserContext.Default.SocketRemoteUser).Result;
}
public async Task<SocketRemoteUser> SendFriendRequest(long user)
{
FriendRequestResult? json = await SendServer("FriendRequest", new FriendRequest() { Id = user, SubType = 0 }, FriendRequestContext.Default.FriendRequest, FriendRequestResultContext.Default.FriendRequestResult);
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<SocketDMChannel>)SocketDMChannelContext.Default.SocketDMChannel);
_ = chan.StartKeyProcessAsync();
chans.Add(chan);
}
}
SocketRemoteUser b = await SocketUserBase.GetUser(user, SocketRemoteUserContext.Default.SocketRemoteUser);
b.FriendStatus = FriendStatus.PendingOut;
return b;
}
public async Task<SocketRemoteUser> SendFriendRequest(string username, short tag)
{
FriendRequestResult json = await SendServer("FriendRequest", new FriendRequest() { Username = username, Tag = tag, SubType = 1 }, FriendRequestContext.Default.FriendRequest, FriendRequestResultContext.Default.FriendRequestResult);
if (json is not null && json.Error is not null)
{
throw (ErrorCode)(int)json.Error switch
{
ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"),
ErrorCode.ServerError => new Exception("Error from server: " + json.ErrorMessage),
ErrorCode.InvalidPostData => 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"),
ErrorCode.Forbidden => new Exception("You already have an outgoing request or the persone is not real"),
_ => new Exception(JsonSerializer.Serialize(json)),
};
}
else if (json is not null && json.Channel is not null && json.Id is not null)
{
SocketDMChannel chan = await SocketChannel.GetChannel(json.Channel.Value, (JsonTypeInfo<SocketDMChannel>)SocketDMChannelContext.Default.SocketDMChannel);
_ = chan.StartKeyProcessAsync();
chans.Add(chan);
return await SocketUserBase.GetUser((long)json.Id, SocketRemoteUserContext.Default.SocketRemoteUser);
}
else throw new Exception("missing data from server");
}
/// <summary>
/// Sends the server a request to update the <paramref name="Status"/> of you account
/// </summary>
/// <param name="Status">The <see cref="UserStatus"/> you want to set your status to</param>
/// <exception cref="Exception"></exception>
public async Task<Task> UpdateStatus(UserStatus Status)
{
if (_user is null) throw new Exception("You must login to make a request");
IncomingHTTP? data = await SendServer("SocketUserProfile/Status", new Status() { UserStatus = Status }, StatusContext.Default.Status, IncomingHTTPContext.Default.IncomingHTTP);
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)
{
if (_user is null) throw new Exception("You must login to make a request");
IncomingHTTP? data = await SendServer("ChangeChannel", new Channel() { Id = Channel }, ChannelContext.Default.Channel, IncomingHTTPContext.Default.IncomingHTTP);
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 async Task<Task> SendMessage(string Message, long Channel, params JsonTypes.File[] Files) => (await GetChannel<SocketTextChannel>(Channel)).SendMessage(Message, Files);
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<SocketMessage> GetMessage(long MessageId) => await SocketMessage.GetMessage(MessageId);
public async Task<SocketRemoteUser> GetUser(long UserID) => await SocketUserBase.GetUser(UserID, SocketRemoteUserContext.Default.SocketRemoteUser);
public async Task<TChannel> GetChannel<TChannel>(long Channel) where TChannel : SocketChannel, new()
{
TChannel Return = new();
switch (Return)
{
case SocketDMChannel:
Return = (await SocketChannel.GetChannel(Channel, SocketDMChannelContext.Default.SocketDMChannel) as TChannel)!;
break;
case SocketGroupChannel:
Return = (await SocketChannel.GetChannel(Channel, SocketGroupChannelContext.Default.SocketGroupChannel) as TChannel)!;
break;
case SocketTextChannel:
Return = (await SocketChannel.GetChannel(Channel, SocketTextChannelContext.Default.SocketTextChannel) as TChannel)!;
break;
case SocketChannel:
Return = (await SocketChannel.GetChannel(Channel, SocketChannelContext.Default.SocketChannel) 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>(Tvalue Payload, JsonTypeInfo<Tvalue> jsonTypeInfo) where Tvalue : IncomingWSS
{
ServerOut?.Send(JsonSerializer.Serialize(Payload, jsonTypeInfo));
}
internal static HttpResponseMessage GetFromServer(string Path, params KeyValuePair<string, string?>[] 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<string, string?> header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value);
return web.GetAsync($"https://{InternalDomain}/{API_Ver}/{Path}").Result;
}
internal static Task GetFromServer(string Path, string File, params KeyValuePair<string, string?>[] 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<string, string?> header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value);
HttpResponseMessage Response = web.GetAsync($"https://{InternalDomain}/{API_Ver}/{Path}").Result;
Stream stream = Response.Content.ReadAsStreamAsync().Result;
using FileStream fs = System.IO.File.Create(File);
stream.CopyTo(fs);
return Task.CompletedTask;
}
internal static async Task<Tresult> GetFromServer<Tresult>(string Path, JsonTypeInfo<Tresult> Type, params KeyValuePair<string, string?>[] Headers) where Tresult : IncomingHTTP, new()
{
HttpResponseMessage ServerResponce = GetFromServer(Path, 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().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<Tresult> SendServer<Tvalue, Tresult>(string Path, Tvalue Payload, JsonTypeInfo<Tvalue> jsonTypeInfo, JsonTypeInfo<Tresult> ReturnjsonTypeInfo, params KeyValuePair<string, string?>[] Headers) where Tvalue : HTTPRequest 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<string, string?> header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value);
HttpResponseMessage ServerResponce = web.PostAsJsonAsync($"https://{InternalDomain}/{API_Ver}/{Path}", Payload, jsonTypeInfo).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().Result)) return error;
try
{
Tresult? temp = JsonSerializer.Deserialize(ServerResponce.Content.ReadAsStreamAsync().Result, ReturnjsonTypeInfo);
if (temp is null) return error;
return temp;
}
catch { return error; }
}
internal static async Task<Tresult> SendServer<Tresult>(string Path, string File, JsonTypeInfo<Tresult> ReturnjsonTypeInfo, params KeyValuePair<string, string?>[] 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<string, string?> header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value);
HttpResponseMessage ServerResponce = web.PostAsync($"https://{InternalDomain}/{API_Ver}/{Path}", new StreamContent(fs)).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().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();
}
}
}

View File

@ -0,0 +1,388 @@
using Luski.net.Enums;
using Luski.net.Interfaces;
using Luski.net.JsonTypes.BaseTypes;
using Luski.net.Sound;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using static Luski.net.Exceptions;
namespace Luski.net.Sockets
{
internal class SocketAudioClient : IAudioClient
{
internal SocketAudioClient(long Channel, Func<Exception, Task>? error)
{
this.Channel = Channel;
errorin = error;
Muted = false;
PrototolClient.DataComplete += new Protocol.DelegateDataComplete(OnProtocolClient_DataComplete);
DataRecived += SocketAudioClient_DataRecived;
}
public event Func<Task>? Connected;
public bool Muted { get; private set; }
public bool Deafened { get; private set; }
public void ToggleMic()
{
if (Muted == true)
{
Muted = false;
}
else
{
Muted = true;
}
}
public void ToggleAudio()
{
if (Deafened == true)
{
Deafened = false;
}
else
{
Deafened = true;
}
}
public void RecordSoundFrom(RecordingDevice Device)
{
if (Connectedb)
{
StartRecordingFromSounddevice_Client(Device);
}
else
{
throw new NotConnectedException(this, "The call has not been connected yet!");
}
}
public void PlaySoundTo(PlaybackDevice Device)
{
if (Connectedb)
{
StartPlayingToSounddevice_Client(Device);
}
else
{
throw new NotConnectedException(this, "The call has not been connected yet!");
}
}
public void JoinCall()
{
if (Connected == null)
{
throw new MissingEventException("Connected");
}
else
{
//get info
string data;
while (true)
{
if (Server.CanRequest)
{
using HttpClient web = new();
web.DefaultRequestHeaders.Add("token", Server.Token);
web.DefaultRequestHeaders.Add("id", Channel.ToString());
data = web.GetAsync($"https://{Server.InternalDomain}/{Server.API_Ver}/GetCallInfo").Result.Content.ReadAsStringAsync().Result;
break;
}
}
call? json = JsonSerializer.Deserialize<call>(data);
Server.SendServer(JsonRequest.Send(DataType.Join_Call, JsonRequest.JoinCall(Channel)).ToString());
Samples = json.samples;
}
}
private class call : IncomingHTTP
{
public int samples { get; set; } = default!;
public string[] members { get; set; } = default!;
}
public void LeaveCall()
{
Server.SendServer(JsonRequest.Send(DataType.Leave_Call, JsonRequest.JoinCall(Channel)).ToString());
StopRecordingFromSounddevice_Client();
}
private readonly Protocol PrototolClient = new(ProtocolTypes.LH, Encoding.Default);
private JitterBuffer RecordingJitterBuffer = new(null, JitterBuffer, 20);
private JitterBuffer PlayingJitterBuffer = new(null, JitterBuffer, 20);
private readonly Func<Exception, Task>? errorin;
private event Func<string, Task> DataRecived;
private static readonly uint JitterBuffer = 5;
private readonly int BitsPerSample = 16;
private long SequenceNumber = 4596;
private readonly int Channels = 1;
private Recorder? RecorderClient;
private bool Connectedb = false;
private bool recording = false;
private long m_TimeStamp = 0;
private Player? PlayerClient;
private readonly long Channel;
private void StopPlayingToSounddevice_Client()
{
if (PlayerClient != null)
{
PlayerClient.Close();
PlayerClient = null;
}
if (PlayingJitterBuffer != null)
{
PlayingJitterBuffer.Stop();
}
}
private async Task SocketAudioClient_DataRecived(string arg)
{
cdata d = JsonSerializer.Deserialize<cdata>(arg);
byte[] data = Convert.FromBase64String(d.data);
PrototolClient.Receive_LH(this, data);
}
private class cdata
{
public string data { get; set; } = default!;
public long from { get; set; } = default!;
}
private void SendData(byte[] data)
{
if (!Connectedb)
{
return;
}
Server.SendServer(JsonRequest.Send(DataType.Call_Data, JsonRequest.SendCallData(PrototolClient.ToBytes(data), Channel)));
}
internal void Givedata(dynamic data)
{
DataRecived.Invoke(((object)data).ToString());
}
private int _samp;
internal int Samples
{
get => _samp;
set
{
_samp = value;
Connectedb = true;
if (Connected is not null) Connected.Invoke();
PlaySoundTo(Devices.GetDefaltPlaybackDevice());
RecordSoundFrom(Devices.GetDefaltRecordingDevice());
}
}
private bool playing = false;
private void StartPlayingToSounddevice_Client(PlaybackDevice device)
{
if (playing)
{
StopPlayingToSounddevice_Client();
}
playing = true;
if (PlayingJitterBuffer != null)
{
PlayingJitterBuffer.DataAvailable -= new JitterBuffer.DelegateDataAvailable(OnJitterBufferClientDataAvailablePlaying);
PlayingJitterBuffer = new JitterBuffer(null, JitterBuffer, 20);
PlayingJitterBuffer.DataAvailable += new JitterBuffer.DelegateDataAvailable(OnJitterBufferClientDataAvailablePlaying);
PlayingJitterBuffer.Start();
}
if (PlayerClient == null)
{
PlayerClient = new Player();
PlayerClient.Open(device.Name, Samples, BitsPerSample, Channels, (int)JitterBuffer);
}
}
private void OnJitterBufferClientDataAvailablePlaying(object sender, RTPPacket rtp)
{
try
{
if (PlayerClient != null)
{
if (PlayerClient.Opened)
{
if (Deafened == false)
{
byte[] linearBytes = Utils.MuLawToLinear(rtp.Data, BitsPerSample, Channels);
PlayerClient.PlayData(linearBytes, false);
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.StackFrame sf = new(true);
errorin?.Invoke(new Exception(string.Format("Exception: {0} StackTrace: {1}. FileName: {2} Method: {3} Line: {4}", ex.Message, ex.StackTrace, sf.GetFileName(), sf.GetMethod(), sf.GetFileLineNumber())));
}
}
private void StartRecordingFromSounddevice_Client(RecordingDevice device)
{
try
{
if (recording)
{
StopRecordingFromSounddevice_Client();
}
recording = true;
InitJitterBufferClientRecording();
int bufferSize = 0;
bufferSize = Utils.GetBytesPerInterval((uint)Samples, BitsPerSample, Channels) * 4;
if (bufferSize > 0)
{
RecorderClient = new Recorder();
RecorderClient.DataRecorded += new Recorder.DelegateDataRecorded(OnDataReceivedFromSoundcard_Client);
if (RecorderClient.Start(device.Name, Samples, BitsPerSample, Channels, 8, bufferSize))
{
RecordingJitterBuffer.Start();
}
}
}
catch (Exception ex)
{
errorin.Invoke(ex);
}
}
private void StopRecordingFromSounddevice_Client()
{
RecorderClient.Stop();
RecorderClient.DataRecorded -= new Recorder.DelegateDataRecorded(OnDataReceivedFromSoundcard_Client);
RecorderClient = null;
RecordingJitterBuffer.Stop();
}
private void InitJitterBufferClientRecording()
{
if (RecordingJitterBuffer != null)
{
RecordingJitterBuffer.DataAvailable -= new JitterBuffer.DelegateDataAvailable(OnJitterBufferClientDataAvailableRecording);
}
RecordingJitterBuffer = new JitterBuffer(null, 8, 20);
RecordingJitterBuffer.DataAvailable += new JitterBuffer.DelegateDataAvailable(OnJitterBufferClientDataAvailableRecording);
}
private void OnJitterBufferClientDataAvailableRecording(object sender, RTPPacket rtp)
{
if (Muted == false && rtp != null && rtp.Data != null && rtp.Data.Length > 0)
{
byte[] rtpBytes = rtp.ToBytes();
SendData(rtpBytes);
}
}
private void OnDataReceivedFromSoundcard_Client(byte[] data)
{
try
{
lock (this)
{
int bytesPerInterval = Utils.GetBytesPerInterval((uint)Samples, BitsPerSample, Channels);
int count = data.Length / bytesPerInterval;
int currentPos = 0;
for (int i = 0; i < count; i++)
{
byte[] partBytes = new byte[bytesPerInterval];
Array.Copy(data, currentPos, partBytes, 0, bytesPerInterval);
currentPos += bytesPerInterval;
RTPPacket rtp = ToRTPPacket(partBytes, BitsPerSample, Channels);
RecordingJitterBuffer.AddData(rtp);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
private RTPPacket ToRTPPacket(byte[] linearData, int bitsPerSample, int channels)
{
byte[] mulaws = Utils.LinearToMulaw(linearData, bitsPerSample, channels);
RTPPacket rtp = new()
{
Data = mulaws,
CSRCCount = 0,
Extension = false,
HeaderLength = RTPPacket.MinHeaderLength,
Marker = false,
Padding = false,
PayloadType = 0,
Version = 2,
SourceId = 0
};
try
{
rtp.SequenceNumber = Convert.ToUInt16(SequenceNumber);
SequenceNumber++;
}
catch (Exception)
{
SequenceNumber = 0;
}
try
{
rtp.Timestamp = Convert.ToUInt32(m_TimeStamp);
m_TimeStamp += mulaws.Length;
}
catch (Exception)
{
m_TimeStamp = 0;
}
return rtp;
}
private void OnProtocolClient_DataComplete(object sender, byte[] data)
{
try
{
if (PlayerClient != null && PlayerClient.Opened)
{
RTPPacket rtp = new(data);
if (rtp.Data != null)
{
if (PlayingJitterBuffer != null)
{
PlayingJitterBuffer.AddData(rtp);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

58
Luski.net/Sound/Devices.cs Executable file
View File

@ -0,0 +1,58 @@
using System.Collections.Generic;
namespace Luski.net.Sound
{
public static class Devices
{
public static RecordingDevice GetDefaltRecordingDevice()
{
return GetRecordingDevices()[0];
}
public static PlaybackDevice GetDefaltPlaybackDevice()
{
return GetPlaybackDevices()[0];
}
public static IReadOnlyList<RecordingDevice> GetRecordingDevices()
{
List<string> RecordingNames = WinSound.GetRecordingNames();
List<RecordingDevice> RecordingDevices = new();
foreach (string Device in RecordingNames)
{
RecordingDevices.Add(new RecordingDevice(Device));
}
return RecordingDevices.AsReadOnly();
}
public static IReadOnlyList<PlaybackDevice> GetPlaybackDevices()
{
List<string> PlaybackName = WinSound.GetPlaybackNames();
List<PlaybackDevice> PlaybackDevices = new();
foreach (string Device in PlaybackName)
{
PlaybackDevices.Add(new PlaybackDevice(Device));
}
return PlaybackDevices.AsReadOnly();
}
}
public class RecordingDevice
{
internal RecordingDevice(string name)
{
Name = name;
}
public string Name { get; }
}
public class PlaybackDevice
{
internal PlaybackDevice(string name)
{
Name = name;
}
public string Name { get; }
}
}

124
Luski.net/Sound/JitterBuffer.cs Executable file
View File

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
namespace Luski.net.Sound
{
internal class JitterBuffer
{
internal JitterBuffer(object? sender, uint maxRTPPackets, uint timerIntervalInMilliseconds)
{
if (maxRTPPackets < 2)
{
throw new Exception("Wrong Arguments. Minimum maxRTPPackets is 2");
}
m_Sender = sender;
Maximum = maxRTPPackets;
IntervalInMilliseconds = timerIntervalInMilliseconds;
Init();
}
private readonly object? m_Sender = null;
private readonly EventTimer m_Timer = new();
private readonly Queue<RTPPacket> m_Buffer = new();
private RTPPacket m_LastRTPPacket = new();
private bool m_Underflow = true;
private bool m_Overflow = false;
internal delegate void DelegateDataAvailable(object sender, RTPPacket packet);
internal event DelegateDataAvailable? DataAvailable;
internal uint Maximum { get; } = 10;
internal uint IntervalInMilliseconds { get; } = 20;
private void Init()
{
InitTimer();
}
private void InitTimer()
{
m_Timer.TimerTick += new EventTimer.DelegateTimerTick(OnTimerTick);
}
internal void Start()
{
m_Timer.Start(IntervalInMilliseconds);
m_Underflow = true;
}
internal void Stop()
{
m_Timer.Stop();
m_Buffer.Clear();
}
private void OnTimerTick()
{
try
{
if (DataAvailable != null)
{
if (m_Buffer.Count > 0)
{
if (m_Overflow)
{
if (m_Buffer.Count <= Maximum / 2)
{
m_Overflow = false;
}
}
if (m_Underflow)
{
if (m_Buffer.Count < Maximum / 2)
{
return;
}
else
{
m_Underflow = false;
}
}
m_LastRTPPacket = m_Buffer.Dequeue();
DataAvailable(m_Sender, m_LastRTPPacket);
}
else
{
m_Overflow = false;
if (m_LastRTPPacket != null && m_Underflow == false)
{
if (m_LastRTPPacket.Data != null)
{
m_Underflow = true;
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("JitterBuffer.cs | OnTimerTick() | {0}", ex.Message));
}
}
internal void AddData(RTPPacket packet)
{
try
{
if (m_Overflow == false)
{
if (m_Buffer.Count <= Maximum)
{
m_Buffer.Enqueue(packet);
}
else
{
m_Overflow = true;
}
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("JitterBuffer.cs | AddData() | {0}", ex.Message));
}
}
}
}

417
Luski.net/Sound/Player.cs Executable file
View File

@ -0,0 +1,417 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Luski.net.Sound
{
internal unsafe class Player
{
internal Player()
{
delegateWaveOutProc = new Win32.DelegateWaveOutProc(WaveOutProc);
}
private readonly LockerClass Locker = new();
private readonly LockerClass LockerCopy = new();
private IntPtr hWaveOut = IntPtr.Zero;
private string WaveOutDeviceName = "";
private bool IsWaveOutOpened = false;
private bool IsThreadPlayWaveOutRunning = false;
private bool IsClosed = false;
private bool IsPaused = false;
private bool IsStarted = false;
private bool IsBlocking = false;
private int SamplesPerSecond = 8000;
private int BitsPerSample = 16;
private int Channels = 1;
private int BufferCount = 8;
private readonly int BufferLength = 1024;
private Win32.WAVEHDR*[] WaveOutHeaders;
private readonly Win32.DelegateWaveOutProc delegateWaveOutProc;
private Thread? ThreadPlayWaveOut;
private readonly AutoResetEvent AutoResetEventDataPlayed = new(false);
internal delegate void DelegateStopped();
internal event DelegateStopped? PlayerClosed;
internal event DelegateStopped? PlayerStopped;
internal bool Opened => IsWaveOutOpened & IsClosed == false;
internal bool Playing
{
get
{
if (Opened && IsStarted)
{
foreach (Win32.WAVEHDR* pHeader in WaveOutHeaders)
{
if (IsHeaderInqueue(*pHeader))
{
return true;
}
}
}
return false;
}
}
private bool CreateWaveOutHeaders()
{
WaveOutHeaders = new Win32.WAVEHDR*[BufferCount];
int createdHeaders = 0;
for (int i = 0; i < BufferCount; i++)
{
WaveOutHeaders[i] = (Win32.WAVEHDR*)Marshal.AllocHGlobal(sizeof(Win32.WAVEHDR));
WaveOutHeaders[i]->dwLoops = 0;
WaveOutHeaders[i]->dwUser = IntPtr.Zero;
WaveOutHeaders[i]->lpNext = IntPtr.Zero;
WaveOutHeaders[i]->reserved = IntPtr.Zero;
WaveOutHeaders[i]->lpData = Marshal.AllocHGlobal(BufferLength);
WaveOutHeaders[i]->dwBufferLength = (uint)BufferLength;
WaveOutHeaders[i]->dwBytesRecorded = 0;
WaveOutHeaders[i]->dwFlags = 0;
Win32.MMRESULT hr = Win32.waveOutPrepareHeader(hWaveOut, WaveOutHeaders[i], sizeof(Win32.WAVEHDR));
if (hr == Win32.MMRESULT.MMSYSERR_NOERROR)
{
createdHeaders++;
}
}
return (createdHeaders == BufferCount);
}
private void FreeWaveOutHeaders()
{
try
{
if (WaveOutHeaders != null)
{
for (int i = 0; i < WaveOutHeaders.Length; i++)
{
Win32.MMRESULT hr = Win32.waveOutUnprepareHeader(hWaveOut, WaveOutHeaders[i], sizeof(Win32.WAVEHDR));
int count = 0;
while (count <= 100 && (WaveOutHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) == Win32.WaveHdrFlags.WHDR_INQUEUE)
{
Thread.Sleep(20);
count++;
}
if ((WaveOutHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) != Win32.WaveHdrFlags.WHDR_INQUEUE)
{
if (WaveOutHeaders[i]->lpData != IntPtr.Zero)
{
Marshal.FreeHGlobal(WaveOutHeaders[i]->lpData);
WaveOutHeaders[i]->lpData = IntPtr.Zero;
}
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.Write(ex.Message);
}
}
private void StartThreadPlayWaveOut()
{
if (IsThreadPlayWaveOutRunning == false)
{
ThreadPlayWaveOut = new System.Threading.Thread(new System.Threading.ThreadStart(OnThreadPlayWaveOut));
IsThreadPlayWaveOutRunning = true;
ThreadPlayWaveOut.Name = "PlayWaveOut";
ThreadPlayWaveOut.Priority = System.Threading.ThreadPriority.Highest;
ThreadPlayWaveOut.Start();
}
}
private bool OpenWaveOut()
{
if (hWaveOut == IntPtr.Zero)
{
if (IsWaveOutOpened == false)
{
Win32.WAVEFORMATEX waveFormatEx = new()
{
wFormatTag = (ushort)Win32.WaveFormatFlags.WAVE_FORMAT_PCM,
nChannels = (ushort)Channels,
nSamplesPerSec = (ushort)SamplesPerSecond,
wBitsPerSample = (ushort)BitsPerSample
};
waveFormatEx.nBlockAlign = (ushort)((waveFormatEx.wBitsPerSample * waveFormatEx.nChannels) >> 3);
waveFormatEx.nAvgBytesPerSec = waveFormatEx.nBlockAlign * waveFormatEx.nSamplesPerSec;
int deviceId = WinSound.GetWaveOutDeviceIdByName(WaveOutDeviceName);
Win32.MMRESULT hr = Win32.waveOutOpen(ref hWaveOut, deviceId, ref waveFormatEx, delegateWaveOutProc, 0, (int)Win32.WaveProcFlags.CALLBACK_FUNCTION);
if (hr != Win32.MMRESULT.MMSYSERR_NOERROR)
{
IsWaveOutOpened = false;
return false;
}
GCHandle.Alloc(hWaveOut, GCHandleType.Pinned);
}
}
IsWaveOutOpened = true;
return true;
}
internal bool Open(string waveOutDeviceName, int samplesPerSecond, int bitsPerSample, int channels, int bufferCount)
{
try
{
lock (Locker)
{
if (Opened == false)
{
WaveOutDeviceName = waveOutDeviceName;
SamplesPerSecond = samplesPerSecond;
BitsPerSample = bitsPerSample;
Channels = channels;
BufferCount = Math.Max(bufferCount, 1);
if (OpenWaveOut())
{
if (CreateWaveOutHeaders())
{
StartThreadPlayWaveOut();
IsClosed = false;
return true;
}
}
}
return false;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Start | {0}", ex.Message));
return false;
}
}
internal bool PlayData(byte[] datas, bool isBlocking)
{
try
{
if (Opened)
{
int index = GetNextFreeWaveOutHeaderIndex();
if (index != -1)
{
IsBlocking = isBlocking;
if (WaveOutHeaders[index]->dwBufferLength != datas.Length)
{
Marshal.FreeHGlobal(WaveOutHeaders[index]->lpData);
WaveOutHeaders[index]->lpData = Marshal.AllocHGlobal(datas.Length);
WaveOutHeaders[index]->dwBufferLength = (uint)datas.Length;
}
WaveOutHeaders[index]->dwBufferLength = (uint)datas.Length;
WaveOutHeaders[index]->dwUser = (IntPtr)index;
Marshal.Copy(datas, 0, WaveOutHeaders[index]->lpData, datas.Length);
IsStarted = true;
Win32.MMRESULT hr = Win32.waveOutWrite(hWaveOut, WaveOutHeaders[index], sizeof(Win32.WAVEHDR));
if (hr == Win32.MMRESULT.MMSYSERR_NOERROR)
{
if (isBlocking)
{
AutoResetEventDataPlayed.WaitOne();
AutoResetEventDataPlayed.Set();
}
return true;
}
else
{
AutoResetEventDataPlayed.Set();
return false;
}
}
else
{
System.Diagnostics.Debug.WriteLine(string.Format("No free WaveOut Buffer found | {0}", DateTime.Now.ToLongTimeString()));
return false;
}
}
else
{
return false;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("PlayData | {0}", ex.Message));
return false;
}
}
internal bool Close()
{
try
{
lock (Locker)
{
if (Opened)
{
IsClosed = true;
int count = 0;
while (Win32.waveOutReset(hWaveOut) != Win32.MMRESULT.MMSYSERR_NOERROR && count <= 100)
{
Thread.Sleep(50);
count++;
}
FreeWaveOutHeaders();
count = 0;
while (Win32.waveOutClose(hWaveOut) != Win32.MMRESULT.MMSYSERR_NOERROR && count <= 100)
{
Thread.Sleep(50);
count++;
}
IsWaveOutOpened = false;
AutoResetEventDataPlayed.Set();
return true;
}
return false;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Close | {0}", ex.Message));
return false;
}
}
private int GetNextFreeWaveOutHeaderIndex()
{
for (int i = 0; i < WaveOutHeaders.Length; i++)
{
if (IsHeaderPrepared(*WaveOutHeaders[i]) && !IsHeaderInqueue(*WaveOutHeaders[i]))
{
return i;
}
}
return -1;
}
private static bool IsHeaderPrepared(Win32.WAVEHDR header)
{
return (header.dwFlags & Win32.WaveHdrFlags.WHDR_PREPARED) > 0;
}
private static bool IsHeaderInqueue(Win32.WAVEHDR header)
{
return (header.dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) > 0;
}
private void WaveOutProc(IntPtr hWaveOut, Win32.WOM_Messages msg, IntPtr dwInstance, Win32.WAVEHDR* pWaveHeader, IntPtr lParam)
{
try
{
switch (msg)
{
//Open
case Win32.WOM_Messages.OPEN:
break;
//Done
case Win32.WOM_Messages.DONE:
IsStarted = true;
AutoResetEventDataPlayed.Set();
break;
//Close
case Win32.WOM_Messages.CLOSE:
IsStarted = false;
IsWaveOutOpened = false;
IsPaused = false;
IsClosed = true;
AutoResetEventDataPlayed.Set();
hWaveOut = IntPtr.Zero;
break;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Player.cs | waveOutProc() | {0}", ex.Message));
AutoResetEventDataPlayed.Set();
}
}
private void OnThreadPlayWaveOut()
{
while (Opened && !IsClosed)
{
AutoResetEventDataPlayed.WaitOne();
lock (Locker)
{
if (Opened && !IsClosed)
{
IsThreadPlayWaveOutRunning = true;
if (!Playing)
{
if (IsStarted)
{
IsStarted = false;
if (PlayerStopped != null)
{
try
{
PlayerStopped();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Player Stopped | {0}", ex.Message));
}
finally
{
AutoResetEventDataPlayed.Set();
}
}
}
}
}
}
if (IsBlocking)
{
AutoResetEventDataPlayed.Set();
}
}
lock (Locker)
{
IsThreadPlayWaveOutRunning = false;
}
if (PlayerClosed != null)
{
try
{
PlayerClosed();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Player Closed | {0}", ex.Message));
}
}
}
}
}

95
Luski.net/Sound/Protocol.cs Executable file
View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Luski.net.Sound
{
internal enum ProtocolTypes
{
LH
}
internal class Protocol
{
internal Protocol(ProtocolTypes type, Encoding encoding)
{
m_ProtocolType = type;
m_Encoding = encoding;
}
private readonly List<byte> m_DataBuffer = new();
private const int m_MaxBufferLength = 10000;
private readonly ProtocolTypes m_ProtocolType = ProtocolTypes.LH;
private readonly Encoding m_Encoding = Encoding.Default;
internal object m_LockerReceive = new();
internal delegate void DelegateDataComplete(object sender, byte[] data);
internal delegate void DelegateExceptionAppeared(object sender, Exception ex);
internal event DelegateDataComplete DataComplete;
internal event DelegateExceptionAppeared ExceptionAppeared;
internal byte[] ToBytes(byte[] data)
{
try
{
byte[] bytesLength = BitConverter.GetBytes(data.Length);
byte[] allBytes = new byte[bytesLength.Length + data.Length];
Array.Copy(bytesLength, allBytes, bytesLength.Length);
Array.Copy(data, 0, allBytes, bytesLength.Length, data.Length);
return allBytes;
}
catch (Exception ex)
{
ExceptionAppeared(null, ex);
}
return data;
}
internal void Receive_LH(object sender, byte[] data)
{
lock (m_LockerReceive)
{
try
{
m_DataBuffer.AddRange(data);
if (m_DataBuffer.Count > m_MaxBufferLength)
{
m_DataBuffer.Clear();
}
byte[] bytes = m_DataBuffer.Take(4).ToArray();
int length = BitConverter.ToInt32(bytes.ToArray(), 0);
if (length > m_MaxBufferLength)
{
m_DataBuffer.Clear();
}
while (m_DataBuffer.Count >= length + 4)
{
byte[] message = m_DataBuffer.Skip(4).Take(length).ToArray();
DataComplete?.Invoke(sender, message);
m_DataBuffer.RemoveRange(0, length + 4);
if (m_DataBuffer.Count > 4)
{
bytes = m_DataBuffer.Take(4).ToArray();
length = BitConverter.ToInt32(bytes.ToArray(), 0);
}
}
}
catch (Exception ex)
{
m_DataBuffer.Clear();
ExceptionAppeared(null, ex);
}
}
}
}
}

139
Luski.net/Sound/RTPPacket.cs Executable file
View File

@ -0,0 +1,139 @@
using System;
using System.Linq;
namespace Luski.net.Sound
{
internal class RTPPacket
{
internal RTPPacket()
{
}
internal RTPPacket(byte[] data)
{
Parse(data);
}
internal static int MinHeaderLength = 12;
internal int HeaderLength = MinHeaderLength;
internal int Version = 0;
internal bool Padding = false;
internal bool Extension = false;
internal int CSRCCount = 0;
internal bool Marker = false;
internal int PayloadType = 0;
internal ushort SequenceNumber = 0;
internal uint Timestamp = 0;
internal uint SourceId = 0;
internal byte[]? Data;
internal ushort ExtensionHeaderId = 0;
internal ushort ExtensionLengthAsCount = 0;
internal int ExtensionLengthInBytes = 0;
private void Parse(byte[] data)
{
if (data.Length >= MinHeaderLength)
{
Version = ValueFromByte(data[0], 6, 2);
Padding = Convert.ToBoolean(ValueFromByte(data[0], 5, 1));
Extension = Convert.ToBoolean(ValueFromByte(data[0], 4, 1));
CSRCCount = ValueFromByte(data[0], 0, 4);
Marker = Convert.ToBoolean(ValueFromByte(data[1], 7, 1));
PayloadType = ValueFromByte(data[1], 0, 7);
HeaderLength = MinHeaderLength + (CSRCCount * 4);
//Sequence Nummer
byte[] seqNum = new byte[2];
seqNum[0] = data[3];
seqNum[1] = data[2];
SequenceNumber = BitConverter.ToUInt16(seqNum, 0);
//TimeStamp
byte[] timeStmp = new byte[4];
timeStmp[0] = data[7];
timeStmp[1] = data[6];
timeStmp[2] = data[5];
timeStmp[3] = data[4];
Timestamp = BitConverter.ToUInt32(timeStmp, 0);
//SourceId
byte[] srcId = new byte[4];
srcId[0] = data[8];
srcId[1] = data[9];
srcId[2] = data[10];
srcId[3] = data[11];
SourceId = BitConverter.ToUInt32(srcId, 0);
if (Extension)
{
byte[] extHeaderId = new byte[2];
extHeaderId[1] = data[HeaderLength + 0];
extHeaderId[0] = data[HeaderLength + 1];
ExtensionHeaderId = BitConverter.ToUInt16(extHeaderId, 0);
byte[] extHeaderLength16 = new byte[2];
extHeaderLength16[1] = data[HeaderLength + 2];
extHeaderLength16[0] = data[HeaderLength + 3];
ExtensionLengthAsCount = BitConverter.ToUInt16(extHeaderLength16.ToArray(), 0);
ExtensionLengthInBytes = ExtensionLengthAsCount * 4;
HeaderLength += ExtensionLengthInBytes + 4;
}
Data = new byte[data.Length - HeaderLength];
Array.Copy(data, HeaderLength, Data, 0, data.Length - HeaderLength);
}
}
private static int ValueFromByte(byte value, int startPos, int length)
{
byte mask = 0;
for (int i = 0; i < length; i++)
{
mask = (byte)(mask | 0x1 << startPos + i);
}
byte result = (byte)((value & mask) >> startPos);
return Convert.ToInt32(result);
}
internal byte[] ToBytes()
{
byte[] bytes = new byte[HeaderLength + Data.Length];
//Byte 0
bytes[0] = (byte)(Version << 6);
bytes[0] |= (byte)(Convert.ToInt32(Padding) << 5);
bytes[0] |= (byte)(Convert.ToInt32(Extension) << 4);
bytes[0] |= (byte)(Convert.ToInt32(CSRCCount));
//Byte 1
bytes[1] = (byte)(Convert.ToInt32(Marker) << 7);
bytes[1] |= (byte)(Convert.ToInt32(PayloadType));
//Byte 2 + 3
byte[] bytesSequenceNumber = BitConverter.GetBytes(SequenceNumber);
bytes[2] = bytesSequenceNumber[1];
bytes[3] = bytesSequenceNumber[0];
//Byte 4 bis 7
byte[] bytesTimeStamp = BitConverter.GetBytes(Timestamp);
bytes[4] = bytesTimeStamp[3];
bytes[5] = bytesTimeStamp[2];
bytes[6] = bytesTimeStamp[1];
bytes[7] = bytesTimeStamp[0];
//Byte 8 bis 11
byte[] bytesSourceId = BitConverter.GetBytes(SourceId);
bytes[8] = bytesSourceId[3];
bytes[9] = bytesSourceId[2];
bytes[10] = bytesSourceId[1];
bytes[11] = bytesSourceId[0];
Array.Copy(Data, 0, bytes, HeaderLength, Data.Length);
return bytes;
}
}
}

340
Luski.net/Sound/Recorder.cs Executable file
View File

@ -0,0 +1,340 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Luski.net.Sound
{
internal unsafe class Recorder
{
internal Recorder()
{
delegateWaveInProc = new Win32.DelegateWaveInProc(WaveInProc);
}
private readonly LockerClass Locker = new();
private readonly LockerClass LockerCopy = new();
private IntPtr hWaveIn = IntPtr.Zero;
private string WaveInDeviceName = "";
private bool IsWaveInOpened = false;
private bool IsWaveInStarted = false;
private bool IsThreadRecordingRunning = false;
private bool IsDataIncomming = false;
private bool Stopped = false;
private int SamplesPerSecond = 8000;
private int BitsPerSample = 16;
private int Channels = 1;
private int BufferCount = 8;
private int BufferSize = 1024;
private Win32.WAVEHDR*[] WaveInHeaders;
private Win32.WAVEHDR* CurrentRecordedHeader;
private readonly Win32.DelegateWaveInProc delegateWaveInProc;
private Thread ThreadRecording;
private readonly AutoResetEvent AutoResetEventDataRecorded = new(false);
internal delegate void DelegateStopped();
internal delegate void DelegateDataRecorded(byte[] bytes);
internal event DelegateStopped RecordingStopped;
internal event DelegateDataRecorded DataRecorded;
internal bool Started => IsWaveInStarted && IsWaveInOpened && IsThreadRecordingRunning;
private bool CreateWaveInHeaders()
{
WaveInHeaders = new Win32.WAVEHDR*[BufferCount];
int createdHeaders = 0;
for (int i = 0; i < BufferCount; i++)
{
WaveInHeaders[i] = (Win32.WAVEHDR*)Marshal.AllocHGlobal(sizeof(Win32.WAVEHDR));
WaveInHeaders[i]->dwLoops = 0;
WaveInHeaders[i]->dwUser = IntPtr.Zero;
WaveInHeaders[i]->lpNext = IntPtr.Zero;
WaveInHeaders[i]->reserved = IntPtr.Zero;
WaveInHeaders[i]->lpData = Marshal.AllocHGlobal(BufferSize);
WaveInHeaders[i]->dwBufferLength = (uint)BufferSize;
WaveInHeaders[i]->dwBytesRecorded = 0;
WaveInHeaders[i]->dwFlags = 0;
Win32.MMRESULT hr = Win32.waveInPrepareHeader(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR));
if (hr == Win32.MMRESULT.MMSYSERR_NOERROR)
{
if (i == 0)
{
hr = Win32.waveInAddBuffer(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR));
}
createdHeaders++;
}
}
return (createdHeaders == BufferCount);
}
private void FreeWaveInHeaders()
{
try
{
if (WaveInHeaders != null)
{
for (int i = 0; i < WaveInHeaders.Length; i++)
{
Win32.MMRESULT hr = Win32.waveInUnprepareHeader(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR));
int count = 0;
while (count <= 100 && (WaveInHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) == Win32.WaveHdrFlags.WHDR_INQUEUE)
{
Thread.Sleep(20);
count++;
}
if ((WaveInHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) != Win32.WaveHdrFlags.WHDR_INQUEUE)
{
if (WaveInHeaders[i]->lpData != IntPtr.Zero)
{
Marshal.FreeHGlobal(WaveInHeaders[i]->lpData);
WaveInHeaders[i]->lpData = IntPtr.Zero;
}
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.Write(ex.Message);
}
}
private void StartThreadRecording()
{
if (Started == false)
{
ThreadRecording = new Thread(new ThreadStart(OnThreadRecording));
IsThreadRecordingRunning = true;
ThreadRecording.Name = "Recording";
ThreadRecording.Priority = ThreadPriority.Highest;
ThreadRecording.Start();
}
}
private bool OpenWaveIn()
{
if (hWaveIn == IntPtr.Zero)
{
if (IsWaveInOpened == false)
{
Win32.WAVEFORMATEX waveFormatEx = new()
{
wFormatTag = (ushort)Win32.WaveFormatFlags.WAVE_FORMAT_PCM,
nChannels = (ushort)Channels,
nSamplesPerSec = (ushort)SamplesPerSecond,
wBitsPerSample = (ushort)BitsPerSample
};
waveFormatEx.nBlockAlign = (ushort)((waveFormatEx.wBitsPerSample * waveFormatEx.nChannels) >> 3);
waveFormatEx.nAvgBytesPerSec = waveFormatEx.nBlockAlign * waveFormatEx.nSamplesPerSec;
int deviceId = WinSound.GetWaveInDeviceIdByName(WaveInDeviceName);
Win32.MMRESULT hr = Win32.waveInOpen(ref hWaveIn, deviceId, ref waveFormatEx, delegateWaveInProc, 0, (int)Win32.WaveProcFlags.CALLBACK_FUNCTION);
if (hWaveIn == IntPtr.Zero)
{
IsWaveInOpened = false;
return false;
}
GCHandle.Alloc(hWaveIn, GCHandleType.Pinned);
}
}
IsWaveInOpened = true;
return true;
}
internal bool Start(string waveInDeviceName, int samplesPerSecond, int bitsPerSample, int channels, int bufferCount, int bufferSize)
{
try
{
lock (Locker)
{
if (Started == false)
{
WaveInDeviceName = waveInDeviceName;
SamplesPerSecond = samplesPerSecond;
BitsPerSample = bitsPerSample;
Channels = channels;
BufferCount = bufferCount;
BufferSize = bufferSize;
if (OpenWaveIn())
{
if (CreateWaveInHeaders())
{
Win32.MMRESULT hr = Win32.waveInStart(hWaveIn);
if (hr == Win32.MMRESULT.MMSYSERR_NOERROR)
{
IsWaveInStarted = true;
StartThreadRecording();
Stopped = false;
return true;
}
else
{
return false;
}
}
}
}
return false;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Start | {0}", ex.Message));
return false;
}
}
internal bool Stop()
{
try
{
lock (Locker)
{
if (Started)
{
Stopped = true;
IsThreadRecordingRunning = false;
CloseWaveIn();
AutoResetEventDataRecorded.Set();
return true;
}
return false;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Stop | {0}", ex.Message));
return false;
}
}
private void CloseWaveIn()
{
Win32.MMRESULT hr = Win32.waveInStop(hWaveIn);
int resetCount = 0;
while (IsAnyWaveInHeaderInState(Win32.WaveHdrFlags.WHDR_INQUEUE) & resetCount < 20)
{
hr = Win32.waveInReset(hWaveIn);
Thread.Sleep(50);
resetCount++;
}
FreeWaveInHeaders();
hr = Win32.waveInClose(hWaveIn);
}
private bool IsAnyWaveInHeaderInState(Win32.WaveHdrFlags state)
{
for (int i = 0; i < WaveInHeaders.Length; i++)
{
if ((WaveInHeaders[i]->dwFlags & state) == state)
{
return true;
}
}
return false;
}
private void WaveInProc(IntPtr hWaveIn, Win32.WIM_Messages msg, IntPtr dwInstance, Win32.WAVEHDR* pWaveHdr, IntPtr lParam)
{
switch (msg)
{
//Open
case Win32.WIM_Messages.OPEN:
break;
//Data
case Win32.WIM_Messages.DATA:
IsDataIncomming = true;
CurrentRecordedHeader = pWaveHdr;
AutoResetEventDataRecorded.Set();
break;
//Close
case Win32.WIM_Messages.CLOSE:
IsDataIncomming = false;
IsWaveInOpened = false;
AutoResetEventDataRecorded.Set();
this.hWaveIn = IntPtr.Zero;
break;
}
}
private void OnThreadRecording()
{
while (Started && !Stopped)
{
AutoResetEventDataRecorded.WaitOne();
try
{
if (Started && !Stopped)
{
if (CurrentRecordedHeader->dwBytesRecorded > 0)
{
if (DataRecorded != null && IsDataIncomming)
{
try
{
byte[] bytes = new byte[CurrentRecordedHeader->dwBytesRecorded];
Marshal.Copy(CurrentRecordedHeader->lpData, bytes, 0, (int)CurrentRecordedHeader->dwBytesRecorded);
DataRecorded(bytes);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Recorder.cs | OnThreadRecording() | {0}", ex.Message));
}
}
for (int i = 0; i < WaveInHeaders.Length; i++)
{
if ((WaveInHeaders[i]->dwFlags & Win32.WaveHdrFlags.WHDR_INQUEUE) == 0)
{
Win32.MMRESULT hr = Win32.waveInAddBuffer(hWaveIn, WaveInHeaders[i], sizeof(Win32.WAVEHDR));
}
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
lock (Locker)
{
IsWaveInStarted = false;
IsThreadRecordingRunning = false;
}
if (RecordingStopped != null)
{
try
{
RecordingStopped();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Format("Recording Stopped | {0}", ex.Message));
}
}
}
}
}

64
Luski.net/Sound/Timer.cs Executable file
View File

@ -0,0 +1,64 @@
using System;
using System.Runtime.InteropServices;
namespace Luski.net.Sound
{
internal class EventTimer
{
internal EventTimer()
{
m_DelegateTimeEvent = new Win32.TimerEventHandler(OnTimer);
}
private bool m_IsRunning = false;
private uint m_Milliseconds = 20;
private uint m_TimerId = 0;
private GCHandle m_GCHandleTimer;
private uint m_UserData = 0;
private uint m_ResolutionInMilliseconds = 0;
private readonly Win32.TimerEventHandler m_DelegateTimeEvent;
internal delegate void DelegateTimerTick();
internal event DelegateTimerTick? TimerTick;
internal void Start(uint milliseconds)
{
m_Milliseconds = milliseconds;
Win32.TimeCaps tc = new();
Win32.TimeGetDevCaps(ref tc, (uint)Marshal.SizeOf(typeof(Win32.TimeCaps)));
m_ResolutionInMilliseconds = Math.Max(tc.wPeriodMin, 0);
Win32.TimeBeginPeriod(m_ResolutionInMilliseconds);
m_TimerId = Win32.TimeSetEvent(m_Milliseconds, m_ResolutionInMilliseconds, m_DelegateTimeEvent, ref m_UserData, Win32.TIME_PERIODIC);
if (m_TimerId > 0)
{
m_GCHandleTimer = GCHandle.Alloc(m_TimerId, GCHandleType.Pinned);
m_IsRunning = true;
}
}
internal void Stop()
{
if (m_TimerId > 0)
{
_ = Win32.TimeKillEvent(m_TimerId);
Win32.TimeEndPeriod(m_ResolutionInMilliseconds);
if (m_GCHandleTimer.IsAllocated)
{
m_GCHandleTimer.Free();
}
m_TimerId = 0;
m_IsRunning = false;
}
}
private void OnTimer(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2)
{
TimerTick?.Invoke();
}
}
}

195
Luski.net/Sound/Utils.cs Executable file
View File

@ -0,0 +1,195 @@
using System;
namespace Luski.net.Sound
{
internal class Utils
{
internal Utils()
{
}
private const int SIGN_BIT = 0x80;
private const int QUANT_MASK = 0xf;
private const int SEG_SHIFT = 4;
private const int SEG_MASK = 0x70;
private const int BIAS = 0x84;
private const int CLIP = 8159;
private static readonly short[] seg_uend = new short[] { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF };
internal static int GetBytesPerInterval(uint SamplesPerSecond, int BitsPerSample, int Channels)
{
int blockAlign = ((BitsPerSample * Channels) >> 3);
int bytesPerSec = (int)(blockAlign * SamplesPerSecond);
uint sleepIntervalFactor = 1000 / 20;
int bytesPerInterval = (int)(bytesPerSec / sleepIntervalFactor);
return bytesPerInterval;
}
internal static int MulawToLinear(int ulaw)
{
ulaw = ~ulaw;
int t = ((ulaw & QUANT_MASK) << 3) + BIAS;
t <<= (ulaw & SEG_MASK) >> SEG_SHIFT;
return ((ulaw & SIGN_BIT) > 0 ? (BIAS - t) : (t - BIAS));
}
private static short Search(short val, short[] table, short size)
{
short i;
int index = 0;
for (i = 0; i < size; i++)
{
if (val <= table[index])
{
return (i);
}
index++;
}
return (size);
}
internal static byte Linear2ulaw(short pcm_val)
{
/* Get the sign and the magnitude of the value. */
pcm_val = (short)(pcm_val >> 2);
short mask;
if (pcm_val < 0)
{
pcm_val = (short)-pcm_val;
mask = 0x7F;
}
else
{
mask = 0xFF;
}
/* clip the magnitude */
if (pcm_val > CLIP)
{
pcm_val = CLIP;
}
pcm_val += (BIAS >> 2);
/* Convert the scaled magnitude to segment number. */
short seg = Search(pcm_val, seg_uend, 8);
/*
* Combine the sign, segment, quantization bits;
* and complement the code word.
*/
/* out of range, return maximum value. */
if (seg >= 8)
{
return (byte)(0x7F ^ mask);
}
else
{
byte uval = (byte)((seg << 4) | ((pcm_val >> (seg + 1)) & 0xF));
return ((byte)(uval ^ mask));
}
}
internal static byte[] MuLawToLinear(byte[] bytes, int bitsPerSample, int channels)
{
int blockAlign = channels * bitsPerSample / 8;
byte[] result = new byte[bytes.Length * blockAlign];
for (int i = 0, counter = 0; i < bytes.Length; i++, counter += blockAlign)
{
int value = MulawToLinear(bytes[i]);
byte[] values = BitConverter.GetBytes(value);
switch (bitsPerSample)
{
case 8:
switch (channels)
{
//8 Bit 1 Channel
case 1:
result[counter] = values[0];
break;
//8 Bit 2 Channel
case 2:
result[counter] = values[0];
result[counter + 1] = values[0];
break;
}
break;
case 16:
switch (channels)
{
//16 Bit 1 Channel
case 1:
result[counter] = values[0];
result[counter + 1] = values[1];
break;
//16 Bit 2 Channels
case 2:
result[counter] = values[0];
result[counter + 1] = values[1];
result[counter + 2] = values[0];
result[counter + 3] = values[1];
break;
}
break;
}
}
return result;
}
internal static byte[] LinearToMulaw(byte[] bytes, int bitsPerSample, int channels)
{
int blockAlign = channels * bitsPerSample / 8;
byte[] result = new byte[bytes.Length / blockAlign];
int resultIndex = 0;
for (int i = 0; i < result.Length; i++)
{
switch (bitsPerSample)
{
case 8:
switch (channels)
{
//8 Bit 1 Channel
case 1:
result[i] = Linear2ulaw(bytes[resultIndex]);
resultIndex += 1;
break;
//8 Bit 2 Channel
case 2:
result[i] = Linear2ulaw(bytes[resultIndex]);
resultIndex += 2;
break;
}
break;
case 16:
switch (channels)
{
//16 Bit 1 Channel
case 1:
result[i] = Linear2ulaw(BitConverter.ToInt16(bytes, resultIndex));
resultIndex += 2;
break;
//16 Bit 2 Channels
case 2:
result[i] = Linear2ulaw(BitConverter.ToInt16(bytes, resultIndex));
resultIndex += 4;
break;
}
break;
}
}
return result;
}
}
}

104
Luski.net/Sound/WaveFile.cs Executable file
View File

@ -0,0 +1,104 @@
using System;
using System.IO;
using System.Text;
namespace Luski.net.Sound
{
internal class WaveFile
{
internal WaveFile()
{
}
internal const int WAVE_FORMAT_PCM = 1;
internal static WaveFileHeader Read(string fileName)
{
WaveFileHeader header = ReadHeader(fileName);
return header;
}
private static WaveFileHeader ReadHeader(string fileName)
{
WaveFileHeader header = new();
if (File.Exists(fileName))
{
FileStream fs = new(fileName, FileMode.Open, FileAccess.Read);
BinaryReader rd = new(fs, Encoding.UTF8);
if (fs.CanRead)
{
header.RIFF = rd.ReadChars(4);
header.RiffSize = (uint)rd.ReadInt32();
header.RiffFormat = rd.ReadChars(4);
header.FMT = rd.ReadChars(4);
header.FMTSize = (uint)rd.ReadInt32();
header.FMTPos = fs.Position;
header.AudioFormat = rd.ReadInt16();
header.Channels = rd.ReadInt16();
header.SamplesPerSecond = (uint)rd.ReadInt32();
header.BytesPerSecond = (uint)rd.ReadInt32();
header.BlockAlign = rd.ReadInt16();
header.BitsPerSample = rd.ReadInt16();
fs.Seek(header.FMTPos + header.FMTSize, SeekOrigin.Begin);
header.DATA = rd.ReadChars(4);
header.DATASize = (uint)rd.ReadInt32();
header.DATAPos = (int)fs.Position;
if (new string(header.DATA).ToUpper() != "DATA")
{
uint DataChunkSize = header.DATASize + 8;
fs.Seek(DataChunkSize, SeekOrigin.Current);
header.DATASize = (uint)(fs.Length - header.DATAPos - DataChunkSize);
}
if (header.DATASize <= fs.Length - header.DATAPos)
{
header.Payload = rd.ReadBytes((int)header.DATASize);
}
}
rd.Close();
fs.Close();
}
return header;
}
}
internal class WaveFileHeader
{
internal WaveFileHeader()
{
}
internal char[] RIFF = new char[4];
internal uint RiffSize = 8;
internal char[] RiffFormat = new char[4];
internal char[] FMT = new char[4];
internal uint FMTSize = 16;
internal short AudioFormat;
internal short Channels;
internal uint SamplesPerSecond;
internal uint BytesPerSecond;
internal short BlockAlign;
internal short BitsPerSample;
internal char[] DATA = new char[4];
internal uint DATASize;
internal byte[] Payload = Array.Empty<byte>();
internal int DATAPos = 44;
internal long FMTPos = 20;
}
}

241
Luski.net/Sound/Win32.cs Executable file
View File

@ -0,0 +1,241 @@
using System;
using System.Runtime.InteropServices;
namespace Luski.net.Sound
{
internal unsafe class Win32
{
internal Win32()
{
}
internal const int WAVE_MAPPER = -1;
internal const int WT_EXECUTEDEFAULT = 0x00000000;
internal const int WT_EXECUTEINIOTHREAD = 0x00000001;
internal const int WT_EXECUTEINTIMERTHREAD = 0x00000020;
internal const int WT_EXECUTEINPERSISTENTTHREAD = 0x00000080;
internal const int TIME_ONESHOT = 0;
internal const int TIME_PERIODIC = 1;
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
internal struct WAVEOUTCAPS
{
internal short wMid;
internal short wPid;
internal int vDriverVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string szPname;
internal uint dwFormats;
internal short wChannels;
internal short wReserved;
internal int dwSupport;
}
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
internal struct WAVEINCAPS
{
internal short wMid;
internal short wPid;
internal int vDriverVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string szPname;
internal uint dwFormats;
internal short wChannels;
internal short wReserved;
internal int dwSupport;
}
[StructLayout(LayoutKind.Sequential)]
internal struct WAVEFORMATEX
{
internal ushort wFormatTag;
internal ushort nChannels;
internal uint nSamplesPerSec;
internal uint nAvgBytesPerSec;
internal ushort nBlockAlign;
internal ushort wBitsPerSample;
internal ushort cbSize;
}
internal enum MMRESULT : uint
{
MMSYSERR_NOERROR = 0,
MMSYSERR_ERROR = 1,
MMSYSERR_BADDEVICEID = 2,
MMSYSERR_NOTENABLED = 3,
MMSYSERR_ALLOCATED = 4,
MMSYSERR_INVALHANDLE = 5,
MMSYSERR_NODRIVER = 6,
MMSYSERR_NOMEM = 7,
MMSYSERR_NOTSUPPORTED = 8,
MMSYSERR_BADERRNUM = 9,
MMSYSERR_INVALFLAG = 10,
MMSYSERR_INVALPARAM = 11,
MMSYSERR_HANDLEBUSY = 12,
MMSYSERR_INVALIDALIAS = 13,
MMSYSERR_BADDB = 14,
MMSYSERR_KEYNOTFOUND = 15,
MMSYSERR_READERROR = 16,
MMSYSERR_WRITEERROR = 17,
MMSYSERR_DELETEERROR = 18,
MMSYSERR_VALNOTFOUND = 19,
MMSYSERR_NODRIVERCB = 20,
WAVERR_BADFORMAT = 32,
WAVERR_STILLPLAYING = 33,
WAVERR_UNPREPARED = 34
}
[Flags]
internal enum WaveHdrFlags : uint
{
WHDR_DONE = 1,
WHDR_PREPARED = 2,
WHDR_BEGINLOOP = 4,
WHDR_ENDLOOP = 8,
WHDR_INQUEUE = 16
}
[Flags]
internal enum WaveProcFlags : int
{
CALLBACK_NULL = 0,
CALLBACK_FUNCTION = 0x30000,
CALLBACK_EVENT = 0x50000,
CALLBACK_WINDOW = 0x10000,
CALLBACK_THREAD = 0x20000,
WAVE_FORMAT_QUERY = 1,
WAVE_MAPPED = 4,
WAVE_FORMAT_DIRECT = 8
}
[Flags]
internal enum HRESULT : long
{
S_OK = 0L,
S_FALSE = 1L
}
[Flags]
internal enum WaveFormatFlags : int
{
WAVE_FORMAT_PCM = 0x0001
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WAVEHDR
{
internal IntPtr lpData;
internal uint dwBufferLength;
internal uint dwBytesRecorded;
internal IntPtr dwUser;
internal WaveHdrFlags dwFlags;
internal uint dwLoops;
internal IntPtr lpNext;
internal IntPtr reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct TimeCaps
{
internal uint wPeriodMin;
internal uint wPeriodMax;
};
internal enum WOM_Messages : int
{
OPEN = 0x03BB,
CLOSE = 0x03BC,
DONE = 0x03BD
}
internal enum WIM_Messages : int
{
OPEN = 0x03BE,
CLOSE = 0x03BF,
DATA = 0x03C0
}
internal delegate void DelegateWaveOutProc(IntPtr hWaveOut, WOM_Messages msg, IntPtr dwInstance, WAVEHDR* pWaveHdr, IntPtr lParam);
internal delegate void DelegateWaveInProc(IntPtr hWaveIn, WIM_Messages msg, IntPtr dwInstance, WAVEHDR* pWaveHdr, IntPtr lParam);
internal delegate void DelegateTimerProc(IntPtr lpParameter, bool TimerOrWaitFired);
internal delegate void TimerEventHandler(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")]
internal static extern uint TimeSetEvent(uint msDelay, uint msResolution, TimerEventHandler handler, ref uint userCtx, uint eventType);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")]
internal static extern uint TimeKillEvent(uint timerId);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeGetDevCaps")]
internal static extern MMRESULT TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeBeginPeriod")]
internal static extern MMRESULT TimeBeginPeriod(uint uPeriod);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeEndPeriod")]
internal static extern MMRESULT TimeEndPeriod(uint uPeriod);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern MMRESULT waveOutOpen(ref IntPtr hWaveOut, int uDeviceID, ref WAVEFORMATEX lpFormat, DelegateWaveOutProc dwCallBack, int dwInstance, int dwFlags);
[DllImport("winmm.dll")]
internal static extern MMRESULT waveInOpen(ref IntPtr hWaveIn, int deviceId, ref WAVEFORMATEX wfx, DelegateWaveInProc dwCallBack, int dwInstance, int dwFlags);
[DllImport("winmm.dll", SetLastError = true)]
internal static extern MMRESULT waveInStart(IntPtr hWaveIn);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern uint waveInGetDevCaps(int index, ref WAVEINCAPS pwic, int cbwic);
[DllImport("winmm.dll", SetLastError = true)]
internal static extern uint waveInGetNumDevs();
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern uint waveOutGetDevCaps(int index, ref WAVEOUTCAPS pwoc, int cbwoc);
[DllImport("winmm.dll", SetLastError = true)]
internal static extern uint waveOutGetNumDevs();
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern MMRESULT waveOutWrite(IntPtr hWaveOut, WAVEHDR* pwh, int cbwh);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "waveOutPrepareHeader", CharSet = CharSet.Auto)]
internal static extern MMRESULT waveOutPrepareHeader(IntPtr hWaveOut, WAVEHDR* lpWaveOutHdr, int uSize);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "waveOutUnprepareHeader", CharSet = CharSet.Auto)]
internal static extern MMRESULT waveOutUnprepareHeader(IntPtr hWaveOut, WAVEHDR* lpWaveOutHdr, int uSize);
[DllImport("winmm.dll", EntryPoint = "waveInStop", SetLastError = true)]
internal static extern MMRESULT waveInStop(IntPtr hWaveIn);
[DllImport("winmm.dll", EntryPoint = "waveInReset", SetLastError = true)]
internal static extern MMRESULT waveInReset(IntPtr hWaveIn);
[DllImport("winmm.dll", EntryPoint = "waveOutReset", SetLastError = true)]
internal static extern MMRESULT waveOutReset(IntPtr hWaveOut);
[DllImport("winmm.dll", SetLastError = true)]
internal static extern MMRESULT waveInPrepareHeader(IntPtr hWaveIn, WAVEHDR* pwh, int cbwh);
[DllImport("winmm.dll", SetLastError = true)]
internal static extern MMRESULT waveInUnprepareHeader(IntPtr hWaveIn, WAVEHDR* pwh, int cbwh);
[DllImport("winmm.dll", EntryPoint = "waveInAddBuffer", SetLastError = true)]
internal static extern MMRESULT waveInAddBuffer(IntPtr hWaveIn, WAVEHDR* pwh, int cbwh);
[DllImport("winmm.dll", SetLastError = true)]
internal static extern MMRESULT waveInClose(IntPtr hWaveIn);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern MMRESULT waveOutClose(IntPtr hWaveOut);
[DllImport("winmm.dll")]
internal static extern MMRESULT waveOutPause(IntPtr hWaveOut);
[DllImport("winmm.dll", EntryPoint = "waveOutRestart", SetLastError = true)]
internal static extern MMRESULT waveOutRestart(IntPtr hWaveOut);
}
}

94
Luski.net/Sound/WinSound.cs Executable file
View File

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Luski.net.Sound
{
internal class LockerClass
{
}
internal class WinSound
{
internal WinSound()
{
}
internal static List<string> GetPlaybackNames()
{
List<string> list = new();
Win32.WAVEOUTCAPS waveOutCap = new();
uint num = Win32.waveOutGetNumDevs();
for (int i = 0; i < num; i++)
{
uint hr = Win32.waveOutGetDevCaps(i, ref waveOutCap, Marshal.SizeOf(typeof(Win32.WAVEOUTCAPS)));
if (hr == (int)Win32.HRESULT.S_OK)
{
list.Add(waveOutCap.szPname);
}
}
return list;
}
internal static List<string> GetRecordingNames()
{
List<string> list = new();
Win32.WAVEINCAPS waveInCap = new();
uint num = Win32.waveInGetNumDevs();
for (int i = 0; i < num; i++)
{
uint hr = Win32.waveInGetDevCaps(i, ref waveInCap, Marshal.SizeOf(typeof(Win32.WAVEINCAPS)));
if (hr == (int)Win32.HRESULT.S_OK)
{
list.Add(waveInCap.szPname);
}
}
return list;
}
internal static int GetWaveInDeviceIdByName(string name)
{
uint num = Win32.waveInGetNumDevs();
Win32.WAVEINCAPS caps = new();
for (int i = 0; i < num; i++)
{
Win32.HRESULT hr = (Win32.HRESULT)Win32.waveInGetDevCaps(i, ref caps, Marshal.SizeOf(typeof(Win32.WAVEINCAPS)));
if (hr == Win32.HRESULT.S_OK)
{
if (caps.szPname == name)
{
return i;
}
}
}
return Win32.WAVE_MAPPER;
}
internal static int GetWaveOutDeviceIdByName(string name)
{
uint num = Win32.waveOutGetNumDevs();
Win32.WAVEOUTCAPS caps = new();
for (int i = 0; i < num; i++)
{
Win32.HRESULT hr = (Win32.HRESULT)Win32.waveOutGetDevCaps(i, ref caps, Marshal.SizeOf(typeof(Win32.WAVEOUTCAPS)));
if (hr == Win32.HRESULT.S_OK)
{
if (caps.szPname == name)
{
return i;
}
}
}
return Win32.WAVE_MAPPER;
}
}
}