540 lines
22 KiB
C#
540 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using JacobTechEncryption;
|
|
using JacobTechEncryption.Enums;
|
|
using Luski.net.Enums;
|
|
using Luski.net.Interfaces;
|
|
using Luski.net.JsonTypes;
|
|
using Luski.net.JsonTypes.HTTP;
|
|
using Luski.net.Structures;
|
|
using Luski.net.Structures.Public;
|
|
using Luski.Shared.PublicServers.V1.ClientToServer.HTTP;
|
|
using Luski.Shared.PublicServers.V1.Enums;
|
|
using Luski.Shared.PublicServers.V1.ServerToClient.HTTP;
|
|
using Luski.Shared.PublicServers.V1.Shared;
|
|
using Role = Luski.net.Structures.Public.Role;
|
|
using SocketChannelProfile = Luski.net.Structures.Public.SocketChannelProfile;
|
|
using SocketUser = Luski.net.Structures.Public.SocketUser;
|
|
|
|
namespace Luski.net;
|
|
|
|
public partial class PublicServer : Server
|
|
{
|
|
public event Func<SocketMessage, Task>? MessageReceived;
|
|
public List<SocketChannel> chans { get; } = new();
|
|
public List<SocketCategory> cats { get; } = new();
|
|
public List<Role> roles { get; } = new();
|
|
|
|
public SocketAppUser User { get; private set; } = null!;
|
|
|
|
private PublicServer(string Domain, string API_Version, bool Secure = true) :
|
|
base(Domain, API_Version, Secure)
|
|
{ }
|
|
|
|
internal static async Task<PublicServer> GetServer(string Domain, string API_Version, bool Secure = true, bool GenerateEncryption = true, bool LogConsole = false)
|
|
{
|
|
DateTime dt = DateTime.UtcNow;
|
|
Console.WriteLine("Connecting to public server '{0}' using API {1}.", Domain, API_Version);
|
|
PublicServer s = new(Domain, API_Version, Secure);
|
|
s.PrintServerMessages = LogConsole;
|
|
if (GenerateEncryption)
|
|
{
|
|
Thread t = new(_ =>
|
|
{
|
|
s.EncryptionHandler.GenerateKeys();
|
|
});
|
|
t.Start();
|
|
}
|
|
ServerInfoSTC? si = null;
|
|
try
|
|
{
|
|
si = await s.GetFromServer("socketserver", ServerInfoSTCContext.Default.ServerInfoSTC, CancellationToken.None);
|
|
s.EncryptionHandler.ServerPublicKey = await (await new HttpClient()
|
|
.GetAsync($"{(s.Secure ? "https" : "http")}://{s.Domain}/{s.ApiVersion}/Keys/PublicKey"))
|
|
.Content
|
|
.ReadAsStringAsync();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LocalServerInfo ServerListing = s.Storage.GetJson(StorageDirectory.ServerInfo, "Servers.json", true,
|
|
LocalServerInfoContext.Default.LocalServerInfo);
|
|
if (ServerListing.AlternateServers.Length > 0)
|
|
{
|
|
Console.WriteLine("Failed to connect to public server '{0}' using API {1}. Attempting to connect to alternate servers.", Domain, API_Version);
|
|
foreach (ServerData Server in ServerListing.AlternateServers)
|
|
{
|
|
s.Secure = Server.Secure;
|
|
s.Domain = Server.DomainAndPort;
|
|
|
|
try
|
|
{
|
|
|
|
si = await s.GetFromServer("socketserver", ServerInfoSTCContext.Default.ServerInfoSTC, CancellationToken.None);
|
|
s.EncryptionHandler.ServerPublicKey = await (await new HttpClient()
|
|
.GetAsync($"{(s.Secure ? "https" : "http")}://{s.Domain}/{s.ApiVersion}/Keys/PublicKey"))
|
|
.Content
|
|
.ReadAsStringAsync();
|
|
Console.WriteLine("Public server '{0}' connection restored by alternate server '{1}' using API {2}.", Domain, s.Domain, API_Version);
|
|
break;
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
if (si is null) throw;
|
|
}
|
|
s.Name = si.Name;
|
|
s.Description = si.Description;
|
|
s.wssurl = si.WSSv4Address;
|
|
s.ServerType = ServerType.Public;
|
|
s.OwnerID = si.Owner;
|
|
Console.WriteLine("Connected to public server '{0}' using API {1} in {4}.\nServer Name: {2}\nServer Description: {3}", Domain, API_Version, s.Name, s.Description, DateTime.UtcNow.Subtract(dt).ToString("g"));
|
|
return s;
|
|
}
|
|
|
|
public long OwnerID { get; private set; } = 0;
|
|
|
|
public async Task<TCategory> GetCategory<TCategory>(long id, CancellationToken CancellationToken) where TCategory : SocketCategory, new()
|
|
{
|
|
CategorySTC request;
|
|
if (cats.Count > 0 && cats.Any(s => s.ID == id))
|
|
{
|
|
return (cats.Where(s => s is TCategory && s.ID == id).First() as TCategory)!;
|
|
}
|
|
while (true)
|
|
{
|
|
if (CanRequest)
|
|
{
|
|
request = await GetFromServer($"SocketCategory", CategorySTCContext.Default.CategorySTC, CancellationToken, new KeyValuePair<string, string?>("id", id.ToString()));
|
|
break;
|
|
}
|
|
}
|
|
if (request is null) throw new Exception("Something was wrong with the server responce");
|
|
if (request.Error is null)
|
|
{
|
|
if (cats.Count > 0 && cats.Any(s => s.ID == request.ID))
|
|
{
|
|
foreach (SocketCategory? p in cats.Where(s => s.ID == request.ID))
|
|
{
|
|
cats.Remove(p);
|
|
}
|
|
}
|
|
|
|
LocalKeyInfo deckey;
|
|
if (request.DescriptionEncryptionKey != 0)
|
|
deckey = EncryptionHandler.GetKey(request.DescriptionEncryptionKey);
|
|
else deckey = new()
|
|
{
|
|
EncryptionType = EncryptionType.None,
|
|
Key = string.Empty
|
|
};
|
|
string dec = deckey.EncryptionType switch
|
|
{
|
|
EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Description), deckey.Key, request.DescriptionEncoderType),
|
|
EncryptionType.AES => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString(
|
|
Encryption.AES.Decrypt(Convert.FromBase64String(request.Description), deckey.Key)),
|
|
_ => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString(Convert.FromBase64String(request.Description))
|
|
};
|
|
LocalKeyInfo nkey;
|
|
if (request.TitleEncryptionKey != 0)
|
|
nkey = EncryptionHandler.GetKey(request.TitleEncryptionKey);
|
|
else nkey = new()
|
|
{
|
|
EncryptionType = EncryptionType.None,
|
|
Key = string.Empty
|
|
};
|
|
string n = nkey.EncryptionType switch
|
|
{
|
|
EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Name), nkey.Key, request.TitleEncoderType),
|
|
EncryptionType.AES => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString(
|
|
Encryption.AES.Decrypt(Convert.FromBase64String(request.Name), nkey.Key)),
|
|
_ => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString(Convert.FromBase64String(request.Name))
|
|
};
|
|
|
|
TCategory bob = new()
|
|
{
|
|
ID = request.ID,
|
|
ParentID = request.Parent,
|
|
Description = dec,
|
|
DescriptionEncoderType = request.DescriptionEncoderType,
|
|
DescriptionEncryptionKey = request.DescriptionEncryptionKey,
|
|
Channels = request.Channels,
|
|
Categories = request.InnerCategories,
|
|
Name = n,
|
|
RoleOverides = request.RoleOverrides,
|
|
UserOverides = request.UserOverrides,
|
|
TitleEncoderType = request.TitleEncoderType,
|
|
TitleEncryptionKey = request.TitleEncryptionKey,
|
|
Server = this,
|
|
Color = new(request.Color)
|
|
};
|
|
cats.Add(bob);
|
|
return bob;
|
|
}
|
|
throw request.Error switch
|
|
{
|
|
ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"),
|
|
ErrorCode.Forbidden => new Exception("The server rejected your request"),
|
|
ErrorCode.ServerError => new Exception("Error from server: " + request.ErrorMessage),
|
|
ErrorCode.InvalidURL or ErrorCode.MissingHeader => new Exception(request.ErrorMessage),
|
|
_ => new Exception($"Unknown data: '{request.ErrorMessage}'"),
|
|
};
|
|
}
|
|
|
|
public async Task<Role> GetRole(long id)
|
|
{
|
|
Role[] r = roles.Where(s => s.ID == id).ToArray();
|
|
if (r.Length > 0) return r[0];
|
|
RoleSTC s = await GetFromServer("SocketRole?id=" + id.ToString(), RoleSTCContext.Default.RoleSTC, CancellationToken.None);
|
|
|
|
Role role = new()
|
|
{
|
|
Server = this,
|
|
ID = s.ID,
|
|
Color = new(s.Color),
|
|
Description = s.Description,
|
|
DisplayName = s.DisplayName,
|
|
MembersListID = s.Members,
|
|
Name = s.Name,
|
|
Index = s.Index,
|
|
ServerPermissions = s.ServerPermissions
|
|
};
|
|
roles.Add(role);
|
|
return role;
|
|
}
|
|
|
|
public async Task<SocketMessage> SendMessage<TChannel>(TChannel channel, string msg, SocketMessage? ReplyTo = null,
|
|
SocketChannelProfile? FakeProfile = null) where TChannel : SocketChannel, new()
|
|
{
|
|
string bc = "";
|
|
if (channel.EncryptionKeys[0] == 0)
|
|
{
|
|
if (!string.IsNullOrEmpty(msg))
|
|
bc = Convert.ToBase64String(Encryption.Generic.Encoders[(int)channel.EncoderTypes[0]].GetBytes(msg));
|
|
}
|
|
else
|
|
{
|
|
LocalKeyInfo key = channel.Server.EncryptionHandler.GetKey(channel.EncryptionKeys[0]);
|
|
bc = Convert.ToBase64String(key.EncryptionType switch
|
|
{
|
|
EncryptionType.RSA => Encryption.RSA.Encrypt(msg, key.Key, channel.EncoderTypes[0]),
|
|
_ => Encryption.AES.Encrypt(Encryption.Generic.Encoders[(int)channel.EncoderTypes[0]].GetBytes(msg), key.Key)
|
|
});
|
|
}
|
|
MessageCTS pcsm = new()
|
|
{
|
|
Files = Array.Empty<long>(),
|
|
ChannelID = channel.ID,
|
|
EncryptionKey = channel.EncryptionKeys[0],
|
|
Encoding = channel.EncoderTypes[0],
|
|
Base64Context = bc
|
|
};
|
|
if (FakeProfile is not null)
|
|
{
|
|
pcsm.Profile = FakeProfile.Id;
|
|
}
|
|
|
|
|
|
|
|
MessageSTC smsg = await channel.Server.SendServer("socketmessage", pcsm,
|
|
MessageCTSContext.Default.MessageCTS, MessageSTCContext.Default.MessageSTC,
|
|
CancellationToken.None);
|
|
List<long> fl = new();
|
|
foreach (var VARIABLE in smsg.Files)
|
|
{
|
|
fl.Add(VARIABLE.ID);
|
|
}
|
|
SocketMessage sm = new()
|
|
{
|
|
ID = smsg.ID,
|
|
AuthorID = smsg.AuthorID,
|
|
ChannelID = channel.ID,
|
|
Context = msg,
|
|
EncoderType = smsg.EncoderType,
|
|
EncryptionKey = smsg.EncryptionKey,
|
|
FileIDs = fl.ToArray(),
|
|
Server = channel.Server,
|
|
IsProfile = smsg.IsProfile
|
|
};
|
|
return sm;
|
|
}
|
|
|
|
public async Task<TChannel> GetChannel<TChannel>(long id, CancellationToken CancellationToken) where TChannel : SocketChannel, new()
|
|
{
|
|
ChannelSTC request;
|
|
if (chans.Count > 0 && chans.Any(s => s.ID == id))
|
|
{
|
|
return (chans.Where(s => s is TChannel && s.ID == id).First() as TChannel)!;
|
|
}
|
|
while (true)
|
|
{
|
|
if (CanRequest)
|
|
{
|
|
request = await GetFromServer($"SocketChannel", ChannelSTCContext.Default.ChannelSTC, CancellationToken, new KeyValuePair<string, string?>("id", id.ToString()));
|
|
break;
|
|
}
|
|
}
|
|
if (request is null) throw new Exception("Something was wrong with the server responce");
|
|
if (request.Error is null)
|
|
{
|
|
if (chans.Count > 0 && chans.Any(s => s.ID == request.ID))
|
|
{
|
|
foreach (SocketChannel? p in chans.Where(s => s.ID == request.ID))
|
|
{
|
|
chans.Remove(p);
|
|
}
|
|
}
|
|
|
|
LocalKeyInfo deckey;
|
|
if (request.DescriptionEncryptionKey != 0)
|
|
deckey = EncryptionHandler.GetKey(request.DescriptionEncryptionKey);
|
|
else deckey = new()
|
|
{
|
|
EncryptionType = EncryptionType.None,
|
|
Key = string.Empty
|
|
};
|
|
string dec = deckey.EncryptionType switch
|
|
{
|
|
EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Description), deckey.Key, request.DescriptionEncoderType),
|
|
EncryptionType.AES => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString(
|
|
Encryption.AES.Decrypt(Convert.FromBase64String(request.Description), deckey.Key)),
|
|
_ => Encryption.Generic.Encoders[(short)request.DescriptionEncoderType].GetString(Convert.FromBase64String(request.Description))
|
|
};
|
|
LocalKeyInfo nkey;
|
|
if (request.TitleEncryptionKey != 0)
|
|
nkey = EncryptionHandler.GetKey(request.TitleEncryptionKey);
|
|
else nkey = new()
|
|
{
|
|
EncryptionType = EncryptionType.None,
|
|
Key = string.Empty
|
|
};
|
|
string n = nkey.EncryptionType switch
|
|
{
|
|
EncryptionType.RSA => Encryption.RSA.Decrypt(Convert.FromBase64String(request.Name), nkey.Key, request.TitleEncoderType),
|
|
EncryptionType.AES => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString(
|
|
Encryption.AES.Decrypt(Convert.FromBase64String(request.Name), nkey.Key)),
|
|
_ => Encryption.Generic.Encoders[(short)request.TitleEncoderType].GetString(Convert.FromBase64String(request.Name))
|
|
};
|
|
|
|
TChannel bob = new()
|
|
{
|
|
ID = request.ID,
|
|
CategoryID = request.Parent,
|
|
Description = dec,
|
|
DescriptionEncoderType = request.DescriptionEncoderType,
|
|
DescriptionEncryptionKey = request.DescriptionEncryptionKey,
|
|
EncoderTypes = request.EncoderTypes,
|
|
EncryptionKeys = request.EncryptionKeys,
|
|
Epoch = request.Epoch,
|
|
Name = n,
|
|
RoleOverrides = request.RoleOverrides,
|
|
UserOverrides = request.UserOverrides,
|
|
Type = request.Type,
|
|
TitleEncoderType = request.TitleEncoderType,
|
|
TitleEncryptionKey = request.TitleEncryptionKey,
|
|
PictureType = request.PictureType,
|
|
Server = this,
|
|
Color = new(request.Color)
|
|
};
|
|
chans.Add(bob);
|
|
return bob;
|
|
}
|
|
throw request.Error switch
|
|
{
|
|
ErrorCode.InvalidToken => new Exception("Your current token is no longer valid"),
|
|
ErrorCode.Forbidden => new Exception("The server rejected your request"),
|
|
ErrorCode.ServerError => new Exception("Error from server: " + request.ErrorMessage),
|
|
ErrorCode.InvalidURL or ErrorCode.MissingHeader => new Exception(request.ErrorMessage),
|
|
_ => new Exception($"Unknown data: '{request.ErrorMessage}'"),
|
|
};
|
|
}
|
|
|
|
public async Task<SocketChannel> MakeChannel(SocketCategory parent, string Name, string Decription)
|
|
{
|
|
ChannelSTC res = await SendServer(
|
|
"SocketChannel",
|
|
new ChannelPostCTS()
|
|
{
|
|
Name = Convert.ToBase64String(Encoding.UTF8.GetBytes(Name)),
|
|
Description = Convert.ToBase64String(Encoding.UTF8.GetBytes(Description)),
|
|
EncoderTypes = new[] { EncoderType.UTF16 },
|
|
EncryptionKeys = new long[] { 0 },
|
|
DescriptionEncoderType = EncoderType.UTF8,
|
|
TitleEncoderType = EncoderType.UTF8,
|
|
Parent = parent.ID,
|
|
DescriptionEncryptionKey = 0,
|
|
TitleEncryptionKey = 0,
|
|
RoleOverrides = Array.Empty<UserRoleOverrideCTS>(),
|
|
UserOverrides = Array.Empty<UserOverrideCTS>(),
|
|
Type = ChannelType.TextAndVoice,
|
|
Color = "FFFFFFFF",
|
|
PictureType = PictureType.none
|
|
},
|
|
ChannelPostCTSContext.Default.ChannelPostCTS,
|
|
ChannelSTCContext.Default.ChannelSTC,
|
|
CancellationToken.None);
|
|
return new SocketChannel()
|
|
{
|
|
ID = res.ID,
|
|
CategoryID = res.Parent,
|
|
Description = Description,
|
|
DescriptionEncoderType = res.DescriptionEncoderType,
|
|
DescriptionEncryptionKey = res.DescriptionEncryptionKey,
|
|
EncoderTypes = res.EncoderTypes,
|
|
EncryptionKeys = res.EncryptionKeys,
|
|
Epoch = res.Epoch,
|
|
Name = Name,
|
|
RoleOverrides = res.RoleOverrides,
|
|
UserOverrides = res.UserOverrides,
|
|
Type = res.Type,
|
|
TitleEncoderType = res.TitleEncoderType,
|
|
TitleEncryptionKey = res.TitleEncryptionKey,
|
|
PictureType = res.PictureType,
|
|
Server = this,
|
|
Color = new(res.Color)
|
|
};
|
|
}
|
|
|
|
public async Task<Tuser> GetUser<Tuser>(long UserId, CancellationToken CancellationToken) where Tuser : SocketUser, new()
|
|
{
|
|
SocketUserSTC user;
|
|
if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId))
|
|
{
|
|
Tuser temp = poeople.Where(s => s is Tuser && s.Id == UserId).Cast<Tuser>().FirstOrDefault()!;
|
|
return temp;
|
|
}
|
|
while (true)
|
|
{
|
|
if (CanRequest)
|
|
{
|
|
user = await GetFromServer("socketuser",
|
|
SocketUserSTCContext.Default.SocketUserSTC,
|
|
CancellationToken,
|
|
new KeyValuePair<string, string?>("id", UserId.ToString()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (user is null) throw new Exception("Server did not return a user");
|
|
if (poeople.Count > 0 && poeople.Any(s => s.Id == UserId))
|
|
{
|
|
foreach (IUser? p in poeople.Where(s => s.Id == UserId))
|
|
{
|
|
poeople.Remove(p);
|
|
}
|
|
}
|
|
if (user is null || user.Error is not null)
|
|
{
|
|
string error = "User was null";
|
|
if (user is not null && user.Error is not null) error = $"{user.Error}: {user.ErrorMessage}";
|
|
throw new Exception($"Something went wrong getting your user information\n{error}");
|
|
}
|
|
|
|
Tuser u = new();
|
|
if (u is SocketUser)
|
|
{
|
|
u = new()
|
|
{
|
|
Server = this,
|
|
Id = user.ID,
|
|
DisplayName = user.DisplayName,
|
|
PictureType = user.PictureType,
|
|
RoleIds = user.RoleIds,
|
|
Status = user.Status
|
|
};
|
|
}
|
|
else
|
|
{
|
|
u = (new SocketAppUser()
|
|
{
|
|
Server = this,
|
|
Id = user.ID,
|
|
DisplayName = user.DisplayName,
|
|
SelectedChannel = user.SelectedChannel,
|
|
PictureType = user.PictureType,
|
|
RoleIds = user.RoleIds,
|
|
Status = user.Status,
|
|
} as Tuser)!;
|
|
}
|
|
poeople.Add(u);
|
|
return u;
|
|
}
|
|
|
|
public async Task<SocketChannelProfile> GetChannelProfile(long ProfileId, CancellationToken CancellationToken)
|
|
{
|
|
ChannelProfileSTC user;
|
|
if (profiles.Count > 0 && profiles.Any(s => s.Id == ProfileId))
|
|
{
|
|
SocketChannelProfile temp = profiles.Where(s => s.Id == ProfileId).FirstOrDefault()!;
|
|
return temp;
|
|
}
|
|
while (true)
|
|
{
|
|
if (CanRequest)
|
|
{
|
|
user = await GetFromServer("socketchannelprofile",
|
|
ChannelProfileSTCContext.Default.ChannelProfileSTC,
|
|
CancellationToken,
|
|
new KeyValuePair<string, string?>("id", ProfileId.ToString()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (user is null) throw new Exception("Server did not return a user");
|
|
if (profiles.Count > 0 && profiles.Any(s => s.Id == ProfileId))
|
|
{
|
|
foreach (SocketChannelProfile? p in profiles.Where(s => s.Id == ProfileId))
|
|
{
|
|
profiles.Remove(p);
|
|
}
|
|
}
|
|
if (user is null || user.Error is not null)
|
|
{
|
|
string error = "User was null";
|
|
if (user is not null && user.Error is not null) error = $"{user.Error}: {user.ErrorMessage}";
|
|
throw new Exception($"Something went wrong getting your user information\n{error}");
|
|
}
|
|
|
|
SocketChannelProfile u = new()
|
|
{
|
|
Server = this,
|
|
Id = user.ID,
|
|
DisplayName = user.DisplayName,
|
|
PictureType = user.PictureType,
|
|
Controllers = user.Controllers,
|
|
Color = new(user.Color)
|
|
};
|
|
poeople.Add(u);
|
|
return u;
|
|
}
|
|
|
|
/// <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, CancellationToken CancellationToken)
|
|
{
|
|
STC? data = await SendServer("SocketUserProfile/Status", new StatusUpdateCTS() { Status = Status }, StatusUpdateCTSContext.Default.StatusUpdateCTS, STCContext.Default.STC, CancellationToken);
|
|
if (data.Error is not null && ((int)data.StatusCode < 200 || (int)data.StatusCode > 299))
|
|
{
|
|
if (data?.ErrorMessage is not null) throw new Exception(data.ErrorMessage);
|
|
if (data?.Error is not null) throw new Exception(((int)data.Error).ToString());
|
|
else throw new Exception("Something went worng");
|
|
}
|
|
|
|
User.Status = Status;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public string Name { get; private set; }
|
|
public string Description { get; private set; }
|
|
} |