From 15adb39115fa2edaef023ed6a1f823af5c10aec7 Mon Sep 17 00:00:00 2001 From: azures04 Date: Sun, 25 Jan 2026 18:54:11 +0100 Subject: [PATCH] Refactor HTTP requests to use HttpHelper utility Introduced a new HttpHelper utility to centralize and standardize HTTP requests across the codebase, replacing direct usage of HttpClient. Updated authentication, asset, game, and Mojang API logic to use the new helper, improving maintainability and consistency. Also refactored game launch options for greater flexibility and configurability. --- src/main/Program.cs | 16 +- src/main/core/Constants.cs | 7 +- src/main/core/auth/Yggdrasil.cs | 3 +- src/main/core/auth/oauth2/Discord.cs | 3 +- src/main/core/game/Assets.cs | 3 +- src/main/core/game/Game.cs | 254 +++++++++++++++++++++------ src/main/core/game/Java.cs | 11 +- src/main/core/game/Libraries.cs | 2 +- src/main/core/game/Versions.cs | 4 +- src/main/core/game/extra/Forge.cs | 6 +- src/main/core/utils/File.cs | 4 +- src/main/core/utils/Http.cs | 32 ++++ src/main/utils/MojangAPI.cs | 36 +--- 13 files changed, 279 insertions(+), 102 deletions(-) create mode 100644 src/main/core/utils/Http.cs diff --git a/src/main/Program.cs b/src/main/Program.cs index 4c0c235..52496f8 100644 --- a/src/main/Program.cs +++ b/src/main/Program.cs @@ -212,8 +212,19 @@ class Program { case "launcher::game": try { - MinecraftVersion version = await GameHelper.PrepareGame("1.12.2", gameRoot, true); - GameHelper.Launch(version, _authenticatedPlayer!, gameRoot, (logLine) => { + var options = new LaunchOptions { + Version = new LaunchOptions.VersionOptions { + Number = "1.12.2", + Type = "release" + }, + Memory = new LaunchOptions.MemoryOptions { + Max = $"{SettingsManager.ReadSettings().Ram.Max}M", + Min = "512M" + }, + ModLoader = LaunchOptions.ModLoaderType.Forge, + }; + MinecraftVersion version = await GameHelper.PrepareGame(options, gameRoot); + GameHelper.Launch(version, _authenticatedPlayer!, gameRoot, options, (logLine) => { var logMessage = new { requestId = "game::log", payload = new { message = logLine.ToString() } @@ -231,6 +242,7 @@ class Program { var response = new { requestId, payload }; window.SendWebMessage(JsonSerializer.Serialize(response, LauncherConstants._jsonOptions)); } catch (Exception ex) { + Console.WriteLine($"Error stacktrace: {ex.StackTrace}"); Console.WriteLine($"Bridge error: {ex.Message}"); } }); diff --git a/src/main/core/Constants.cs b/src/main/core/Constants.cs index 6a5bc3f..7203259 100644 --- a/src/main/core/Constants.cs +++ b/src/main/core/Constants.cs @@ -1,11 +1,8 @@ using System.Net.Http; using System.Text.Json; -namespace Lentia.Core.Constants -{ - public static class LauncherConstants - { - public static readonly HttpClient Http = new HttpClient(); +namespace Lentia.Core.Constants { + public static class LauncherConstants { public static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public static class Urls { diff --git a/src/main/core/auth/Yggdrasil.cs b/src/main/core/auth/Yggdrasil.cs index f5e0c09..1ad5b57 100644 --- a/src/main/core/auth/Yggdrasil.cs +++ b/src/main/core/auth/Yggdrasil.cs @@ -1,6 +1,7 @@ using Lentia.Core.Constants; using System.Text.Json.Serialization; using System.Net.Http.Json; +using Lentia.Core.Utils; namespace Lentia.Core.Auth.Yggdrasil; @@ -76,7 +77,7 @@ public class Authenticator { }; try { - var response = await LauncherConstants.Http.PostAsJsonAsync(LauncherConstants.Urls.MojangAuthServer + "/authenticate", payload); + var response = await HttpHelper.FetchAsync(LauncherConstants.Urls.MojangAuthServer + "/authenticate", HttpMethod.Post, payload); if (!response.IsSuccessStatusCode) { var errorData = await response.Content.ReadFromJsonAsync(); return AuthResult.Fail( diff --git a/src/main/core/auth/oauth2/Discord.cs b/src/main/core/auth/oauth2/Discord.cs index 9b6f572..d2724e3 100644 --- a/src/main/core/auth/oauth2/Discord.cs +++ b/src/main/core/auth/oauth2/Discord.cs @@ -3,6 +3,7 @@ using System.Net.Http.Json; using System.Text.Json; using Lentia.Core.Auth.Yggdrasil; using Lentia.Core.Constants; +using Lentia.Core.Utils; namespace Lentia.Core.Auth.OAuth2; @@ -36,7 +37,7 @@ public class Discord { public static async Task LoginWithDiscordOAuth2(string authCode) { var url = $"https://yggdrasil.azures.fr/auth/provider/discord/login/callback?code={authCode}&requestUser=true"; try { - var response = await LauncherConstants.Http.GetAsync(url); + var response = await HttpHelper.FetchAsync(url, HttpMethod.Get); if (response.IsSuccessStatusCode) { var data = await response.Content.ReadFromJsonAsync(); return data != null ? AuthResult.Ok(data) : AuthResult.Fail("Erreur de lecture des données."); diff --git a/src/main/core/game/Assets.cs b/src/main/core/game/Assets.cs index e67d565..e67cfd3 100644 --- a/src/main/core/game/Assets.cs +++ b/src/main/core/game/Assets.cs @@ -25,7 +25,7 @@ public class AssetsHelper { } public static async Task GetAssetsIndexAsync(string url) { - var response = await LauncherConstants.Http.GetAsync(url); + var response = await HttpHelper.FetchAsync(url, HttpMethod.Get); if (response.IsSuccessStatusCode) { string json = await response.Content.ReadAsStringAsync(); @@ -86,6 +86,7 @@ public class AssetsHelper { try { await FileHelper.DownloadFileAsync(url, localPath); } catch (Exception ex) { + Console.WriteLine(url); Console.WriteLine($"Échec : {hash} - {ex.Message}"); } }); diff --git a/src/main/core/game/Game.cs b/src/main/core/game/Game.cs index fc012d2..5c22b66 100644 --- a/src/main/core/game/Game.cs +++ b/src/main/core/game/Game.cs @@ -1,55 +1,164 @@ using System.Diagnostics; +using System.Text.Json; using Lentia.Core.Auth.Yggdrasil; +using Lentia.Core.Constants; using Lentia.Core.Game.Extra; using Lentia.Core.Utils; using Lentia.Utils; namespace Lentia.Core.Game; + +public class LaunchOptions { + + public enum ModLoaderType { + Vanilla, + Forge, + Fabric, + NeoForge + } + + public VersionOptions Version { get; set; } = new(); + public ModLoaderType ModLoader { get; set; } = ModLoaderType.Vanilla; + public string? Forge { get; set; } + + public MemoryOptions Memory { get; set; } = new(); + public string JavaPath { get; set; } = "java"; + public int Timeout { get; set; } = 10000; + + public List CustomArgs { get; set; } = new(); + public List CustomLaunchArgs { get; set; } = new(); + public List Features { get; set; } = new(); + + public WindowOptions Window { get; set; } = new(); + + public QuickPlayOptions? QuickPlay { get; set; } + + public ProxyOptions? Proxy { get; set; } + + public OverridesOptions Overrides { get; set; } = new(); + + public class VersionOptions { + public string Number { get; set; } = "1.12.2"; + public string Type { get; set; } = "release"; + public string? Custom { get; set; } + } + + public class MemoryOptions { + public string Max { get; set; } = "2048M"; + public string Min { get; set; } = "512M"; + } + + public class WindowOptions { + public int? Width { get; set; } + public int? Height { get; set; } + public bool Fullscreen { get; set; } = false; + } + + public class QuickPlayOptions { + public string Type { get; set; } = "singleplayer"; + public string? Identifier { get; set; } + public string? Path { get; set; } + } + + public class ProxyOptions { + public string? Host { get; set; } + public string Port { get; set; } = "8080"; + public string? Username { get; set; } + public string? Password { get; set; } + } + + public class OverridesOptions { + public string? GameDirectory { get; set; } + public string? MinecraftJar { get; set; } + public string? VersionName { get; set; } + public string? VersionJson { get; set; } + public string? Directory { get; set; } + public string? Natives { get; set; } + public string? AssetRoot { get; set; } + public string? AssetIndex { get; set; } + public string? LibraryRoot { get; set; } + public string? Cwd { get; set; } + public bool Detached { get; set; } = false; + public List? Classes { get; set; } + public string? Log4jConfigurationFile { get; set; } + + public UrlOverrides Url { get; set; } = new(); + } + + public class UrlOverrides { + public string Meta { get; set; } = "https://launchermeta.mojang.com"; + public string Resource { get; set; } = "https://resources.download.minecraft.net"; + public string MavenForge { get; set; } = "https://maven.minecraftforge.net/"; + public string DefaultRepoForge { get; set; } = "https://libraries.minecraft.net/"; + public string FallbackMaven { get; set; } = "https://search.maven.org/remotecontent?filepath="; + } +} + public static class GameHelper { - public static async Task PrepareGame(string targetVersion, string gameRoot, bool installForge) { - try { - string versionUrl = await VersionsHelper.GetVersionUrlAsync(targetVersion); - MinecraftVersion version = await VersionsHelper.GetVersionDetailsAsync(versionUrl); - MinecraftVersion workingVersion = version; + public static async Task PrepareGame(LaunchOptions options, string gameRoot) { + string root = options.Overrides.GameDirectory ?? gameRoot; - if (installForge) { - MinecraftVersion? forgeMeta = await ForgeHelper.ProcessForge(version, gameRoot); - if (forgeMeta != null) { - workingVersion = version; - } - } - - AssetsIndex assetsIndex = await AssetsHelper.GetAssetsIndexAsync(version.AssetIndex!.Url!); - await AssetsHelper.DownloadAssetsParallel(assetsIndex, gameRoot); - await JavaHelper.GetJavaExecutablePath(version, gameRoot); - await VersionsHelper.DownloadClientJar(version, gameRoot); - await LibrariesHelper.DownloadLibraries(workingVersion.Libraries!, gameRoot); - LibrariesHelper.ExtractNatives(workingVersion, gameRoot); - await VersionsHelper.DownloadLoggingConfig(version, gameRoot); - - return workingVersion; - } catch (Exception) { - throw; + MinecraftVersion baseVersion; + if (!string.IsNullOrEmpty(options.Overrides.VersionJson)) { + string jsonContent = await File.ReadAllTextAsync(options.Overrides.VersionJson); + baseVersion = JsonSerializer.Deserialize(jsonContent, LauncherConstants._jsonOptions)!; + } else { + string versionUrl = await VersionsHelper.GetVersionUrlAsync(options.Version.Number); + baseVersion = await VersionsHelper.GetVersionDetailsAsync(versionUrl); } + + MinecraftVersion workingVersion = baseVersion; + + switch (options.ModLoader) { + case LaunchOptions.ModLoaderType.Forge: + var forgeMeta = await ForgeHelper.ProcessForge(baseVersion, root); + if (forgeMeta != null) { + workingVersion = baseVersion; + } + break; + } + + if (baseVersion.AssetIndex == null) + throw new Exception("Erreur : L'index des assets est manquant dans le manifest de version."); + + string assetRoot = options.Overrides.AssetRoot ?? Path.Combine(root, "assets"); + AssetsIndex assetsIndex = await AssetsHelper.GetAssetsIndexAsync(baseVersion.AssetIndex.Url!); + await AssetsHelper.DownloadAssetsParallel(assetsIndex, assetRoot); + + string libRoot = options.Overrides.LibraryRoot ?? Path.Combine(root, "libraries"); + await LibrariesHelper.DownloadLibraries(workingVersion.Libraries!, libRoot); + + await VersionsHelper.DownloadClientJar(baseVersion, root); + LibrariesHelper.ExtractNatives(workingVersion, root); + + return workingVersion; } public static string BuildClasspath(MinecraftVersion version, string gameRoot) { var paths = new List(); string libRoot = Path.Combine(gameRoot, "libraries"); - paths.Add(Path.Combine(gameRoot, "versions", version.Id!, $"{version.Id}.jar")); + string clientJar = Path.Combine(gameRoot, "versions", version.Id!, $"{version.Id}.jar"); + if (File.Exists(clientJar)) paths.Add(clientJar); - foreach (var lib in version.Libraries!) { - if (LibrariesHelper.IsAllowed(lib.Rules, OsHelper.GetOSName())) { - string relPath = ForgeHelper.GeneratePathFromArtifactName(lib.Name!); - paths.Add(Path.Combine(libRoot, relPath)); + if (version.Libraries != null) { + foreach (var lib in version.Libraries) { + if (!LibrariesHelper.IsAllowed(lib.Rules, OsHelper.GetOSName())) continue; + + string? relPath = lib.Downloads?.Artifact?.Path ?? ForgeHelper.GeneratePathFromArtifactName(lib.Name!); + + string fullPath = Path.Combine(libRoot, relPath); + + if (File.Exists(fullPath)) { + paths.Add(fullPath); + } else { + Console.WriteLine($"[Classpath] Manquant: {fullPath}"); + } } } - - string separator = Path.PathSeparator.ToString(); - return string.Join(separator, paths); + return string.Join(Path.PathSeparator.ToString(), paths); } public static List GenerateGameArguments(MinecraftVersion version, AuthenticateResponse auth, string gameRoot) { @@ -73,52 +182,89 @@ public static class GameHelper { return rawArgs.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList(); } - public static void Launch(MinecraftVersion version, AuthenticateResponse auth, string gameRoot, Action logHandler) { - string javaPath = SettingsManager.ReadSettings().JavaPath; + public static void Launch(MinecraftVersion version, AuthenticateResponse auth, string gameRoot, LaunchOptions options, Action logHandler) { + string root = options.Overrides.GameDirectory ?? gameRoot; + string nativesDir = options.Overrides.Natives ?? Path.Combine(root, "versions", version.Id!, "natives"); + string javaPath = options.JavaPath ?? SettingsManager.ReadSettings().JavaPath; - string nativesDir = Path.Combine(gameRoot, "versions", version.Id!, "natives"); - var jvmArgs = new List { - $"-Xmx{SettingsManager.ReadSettings().Ram.Max}M", + $"-Xms{options.Memory.Min}", + $"-Xmx{options.Memory.Max}", $"-Djava.library.path={nativesDir}", "-Dminecraft.launcher.brand=Lentia", "-Dminecraft.launcher.version=1.0.0" }; - // if (version.Logging?.Client?.Argument != null) { - // string logConfig = Path.Combine(gameRoot, "assets", "log_configs", version.Logging.Client.File?.Id ?? "client-log4j2.xml"); - // jvmArgs.Add(version.Logging.Client.Argument.Replace("${path}", logConfig)); + // string? logConfigFile = options.Overrides.Log4jConfigurationFile; + // if (string.IsNullOrEmpty(logConfigFile) && version.Logging?.Client?.Argument != null) { + // logConfigFile = Path.Combine(root, "assets", "log_configs", version.Logging.Client.File?.Id ?? "client-log4j2.xml"); + // jvmArgs.Add(version.Logging.Client.Argument.Replace("${path}", logConfigFile)); + // } else if (!string.IsNullOrEmpty(logConfigFile)) { + // jvmArgs.Add($"-Dlog4j.configurationFile={logConfigFile}"); // } - string classpath = BuildClasspath(version, gameRoot); + if (options.CustomArgs != null && options.CustomArgs.Any()) { + jvmArgs.AddRange(options.CustomArgs); + } + + string classpath = (options.Overrides.Classes != null && options.Overrides.Classes.Any()) + ? string.Join(Path.PathSeparator.ToString(), options.Overrides.Classes) + : BuildClasspath(version, root); + jvmArgs.Add("-cp"); jvmArgs.Add(classpath); jvmArgs.Add(version.MainClass ?? "net.minecraft.client.main.Main"); - var gameArgs = GenerateGameArguments(version, auth, gameRoot); - jvmArgs.AddRange(gameArgs); + var gameArgs = GenerateGameArguments(version, auth, root); + + if (options.QuickPlay != null) { + gameArgs.Add($"--quickPlayPath {options.QuickPlay.Path}"); + gameArgs.Add($"--quickPlay{options.QuickPlay.Type} {options.QuickPlay.Identifier}"); + } + + if (options.Window.Fullscreen) { + gameArgs.Add("--fullscreen"); + } + if (options.Window.Width.HasValue) { + gameArgs.Add("--width"); gameArgs.Add(options.Window.Width.Value.ToString()); + } + if (options.Window.Height.HasValue) { + gameArgs.Add("--height"); gameArgs.Add(options.Window.Height.Value.ToString()); + } + + if (options.CustomLaunchArgs != null && options.CustomLaunchArgs.Any()) { + gameArgs.AddRange(options.CustomLaunchArgs); + } var startInfo = new ProcessStartInfo(javaPath) { - WorkingDirectory = gameRoot, - Arguments = string.Join(" ", jvmArgs.Select(a => a.Contains(" ") ? $"\"{a}\"" : a)), - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; + WorkingDirectory = options.Overrides.Cwd ?? root, + Arguments = string.Join(" ", jvmArgs.Concat(gameArgs).Select(a => a.Contains(" ") ? $"\"{a}\"" : a)), + UseShellExecute = options.Overrides.Detached, + RedirectStandardOutput = !options.Overrides.Detached, + RedirectStandardError = !options.Overrides.Detached, + CreateNoWindow = true + }; + + Console.WriteLine($"Start command: {javaPath} {startInfo.Arguments}"); var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; - process.OutputDataReceived += (s, e) => { if (e.Data != null) logHandler(e.Data); }; - process.ErrorDataReceived += (s, e) => { if (e.Data != null) logHandler($"[ERROR] {e.Data}"); }; + if (!options.Overrides.Detached) { + process.OutputDataReceived += (s, e) => { if (e.Data != null) logHandler(e.Data); }; + process.ErrorDataReceived += (s, e) => { if (e.Data != null) logHandler($"[ERROR] {e.Data}"); }; + } + process.Exited += (sender, e) => { logHandler("GAME_CLOSED"); }; - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); + if (process.Start()) { + if (!options.Overrides.Detached) { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + } } } \ No newline at end of file diff --git a/src/main/core/game/Java.cs b/src/main/core/game/Java.cs index d80e2e1..116e593 100644 --- a/src/main/core/game/Java.cs +++ b/src/main/core/game/Java.cs @@ -70,7 +70,10 @@ public static class JavaHelper { private static async Task DownloadJavaRuntime(string root, string platform, string component) { string allJsonUrl = "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc6c0dba9f635e09f3900331038590632291/all.json"; - var allRuntimes = await LauncherConstants.Http.GetFromJsonAsync(allJsonUrl); + + var allRuntimesResponse = await HttpHelper.FetchAsync(allJsonUrl); + allRuntimesResponse.EnsureSuccessStatusCode(); + var allRuntimes = await allRuntimesResponse.Content.ReadFromJsonAsync(LauncherConstants._jsonOptions); if (!allRuntimes.TryGetProperty(platform, out var platformData) || !platformData.TryGetProperty(component, out var componentVersions)) { @@ -78,15 +81,19 @@ public static class JavaHelper { } string manifestUrl = componentVersions[0].GetProperty("manifest").GetProperty("url").GetString()!; - var manifest = await LauncherConstants.Http.GetFromJsonAsync(manifestUrl); + var manifestResponse = await HttpHelper.FetchAsync(manifestUrl); + manifestResponse.EnsureSuccessStatusCode(); + var manifest = await manifestResponse.Content.ReadFromJsonAsync(LauncherConstants._jsonOptions); string javaDir = Path.Combine(root, "runtime", component, platform); await Parallel.ForEachAsync(manifest!.Files!, new ParallelOptions { MaxDegreeOfParallelism = 8 }, async (entry, token) => { string localPath = Path.Combine(javaDir, entry.Key); + if (entry.Value.Type == "directory") { Directory.CreateDirectory(localPath); } else if (entry.Value.Downloads?.Raw != null) { var download = entry.Value.Downloads.Raw; + if (!File.Exists(localPath) || new FileInfo(localPath).Length != download.Size) { await FileHelper.DownloadFileAsync(download.Url!, localPath); } diff --git a/src/main/core/game/Libraries.cs b/src/main/core/game/Libraries.cs index 816a196..21d5b83 100644 --- a/src/main/core/game/Libraries.cs +++ b/src/main/core/game/Libraries.cs @@ -7,7 +7,7 @@ public static class LibrariesHelper { public static async Task DownloadLibraries(List libraries, string root) { string osName = OsHelper.GetOSName(); - string librariesDir = Path.Combine(root, "libraries"); + string librariesDir = Path.Combine(root); var downloadsToProcess = new List(); diff --git a/src/main/core/game/Versions.cs b/src/main/core/game/Versions.cs index dd49711..3241734 100644 --- a/src/main/core/game/Versions.cs +++ b/src/main/core/game/Versions.cs @@ -196,7 +196,7 @@ public class JavaVersionInfo { public static class VersionsHelper { public static async Task GetVersionUrlAsync(string targetVersion) { - var response = await LauncherConstants.Http.GetAsync("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"); + var response = await HttpHelper.FetchAsync("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", HttpMethod.Get); var manifest = await response.Content.ReadFromJsonAsync(); var versionEntry = manifest?.Versions?.FirstOrDefault(v => v.Id == targetVersion); @@ -206,7 +206,7 @@ public static class VersionsHelper { } public static async Task GetVersionDetailsAsync(string versionUrl) { - var response = await LauncherConstants.Http.GetAsync(versionUrl); + var response = await HttpHelper.FetchAsync(versionUrl, HttpMethod.Get); if (response.IsSuccessStatusCode) { string jsonResponse = await response.Content.ReadAsStringAsync(); diff --git a/src/main/core/game/extra/Forge.cs b/src/main/core/game/extra/Forge.cs index fee7b22..60b5a62 100644 --- a/src/main/core/game/extra/Forge.cs +++ b/src/main/core/game/extra/Forge.cs @@ -16,9 +16,11 @@ public static class ForgeHelper { public static async Task FetchAndDownloadForge(string mcVersion, string root, bool recommended = true) { string promoUrl = "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"; - LauncherConstants.Http.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + var response = await HttpHelper.FetchAsync(promoUrl, HttpMethod.Get); - var promoData = await LauncherConstants.Http.GetFromJsonAsync(promoUrl); + response.EnsureSuccessStatusCode(); + + var promoData = await response.Content.ReadFromJsonAsync(LauncherConstants._jsonOptions); string promoKey = $"{mcVersion}-{(recommended ? "recommended" : "latest")}"; if (promoData?.Promos == null || !promoData.Promos.TryGetValue(promoKey, out string? buildVersion)) { diff --git a/src/main/core/utils/File.cs b/src/main/core/utils/File.cs index 1ac73dc..a70053f 100644 --- a/src/main/core/utils/File.cs +++ b/src/main/core/utils/File.cs @@ -1,5 +1,3 @@ -using Lentia.Core.Constants; - namespace Lentia.Core.Utils; public static class FileHelper { @@ -13,7 +11,7 @@ public static class FileHelper { File.Delete(destinationPath); } - using var response = await LauncherConstants.Http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + using var response = await HttpHelper.FetchAsync(url); response.EnsureSuccessStatusCode(); using var streamToReadFrom = await response.Content.ReadAsStreamAsync(); diff --git a/src/main/core/utils/Http.cs b/src/main/core/utils/Http.cs new file mode 100644 index 0000000..6556fae --- /dev/null +++ b/src/main/core/utils/Http.cs @@ -0,0 +1,32 @@ +using System.Net.Http.Json; +using Lentia.Core.Constants; + +namespace Lentia.Core.Utils; + +public static class HttpHelper { + + private static readonly HttpClient Http = new HttpClient(); + + public static async Task FetchAsync(string url, HttpMethod? method = null, object? body = null, Dictionary? headers = null) { + var request = new HttpRequestMessage(method ?? HttpMethod.Get, url); + + request.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + + if (headers != null) { + foreach (var header in headers) { + request.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + + if (body != null) { + if (body is string s) { + request.Content = new StringContent(s, System.Text.Encoding.UTF8, "application/json"); + } else { + request.Content = JsonContent.Create(body, options: LauncherConstants._jsonOptions); + } + } + + return await Http.SendAsync(request); + } + +} \ No newline at end of file diff --git a/src/main/utils/MojangAPI.cs b/src/main/utils/MojangAPI.cs index 19b1e6f..25cf69f 100644 --- a/src/main/utils/MojangAPI.cs +++ b/src/main/utils/MojangAPI.cs @@ -1,27 +1,23 @@ using System.Net.Http.Headers; -using System.Text; using System.Text.Json; using Lentia.Core.Constants; +using Lentia.Core.Utils; namespace Lentia.Utils; public static class MojangAPI { public static async Task UploadSkinAsync(string filePath, string variant, string token) { - LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - using var content = new MultipartFormDataContent(); - content.Add(new StringContent(variant), "variant"); byte[] fileBytes = await File.ReadAllBytesAsync(filePath); var fileContent = new ByteArrayContent(fileBytes); fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png"); - content.Add(fileContent, "file", Path.GetFileName(filePath)); - var response = await LauncherConstants.Http.PostAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/skins", content); - + var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/skins", method: HttpMethod.Post, body: content, headers: new() { { "Authorization", $"Bearer {token}" } } ); + if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } else { @@ -29,12 +25,8 @@ public static class MojangAPI { } } - public static async Task GetPlayerProfileAsync(string accessToken) { - LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - LauncherConstants.Http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - var response = await LauncherConstants.Http.GetAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile"); + var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile", headers: new() { { "Authorization", $"Bearer {accessToken}" }, { "Accept", "application/json" } } ); if (response.IsSuccessStatusCode) { string jsonResponse = await response.Content.ReadAsStringAsync(); @@ -45,9 +37,7 @@ public static class MojangAPI { } public static async Task HideCapeAsync(string accessToken) { - LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var response = await LauncherConstants.Http.DeleteAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active"); + var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active", method: HttpMethod.Delete, headers: new() { { "Authorization", $"Bearer {accessToken}" } } ); if (response.IsSuccessStatusCode) { string jsonResponse = await response.Content.ReadAsStringAsync(); @@ -58,13 +48,7 @@ public static class MojangAPI { } public static async Task ShowCapeAsync(string accessToken, string capeId) { - LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var body = new { capeId = capeId }; - string jsonBody = JsonSerializer.Serialize(body); - var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); - - var response = await LauncherConstants.Http.PutAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active", content); + var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active", method: HttpMethod.Put, body: new { capeId = capeId }, headers: new() { { "Authorization", $"Bearer {accessToken}" } } ); if (response.IsSuccessStatusCode) { string jsonResponse = await response.Content.ReadAsStringAsync(); @@ -75,18 +59,14 @@ public static class MojangAPI { } public static async Task ChangeUsernameAsync(string newName, string accessToken) { - LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - var url = $"https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/name/{newName}"; - var request = new HttpRequestMessage(HttpMethod.Put, url); - var response = await LauncherConstants.Http.SendAsync(request); + var response = await HttpHelper.FetchAsync(url, method: HttpMethod.Put, headers: new() { { "Authorization", $"Bearer {accessToken}" } } ); if (response.IsSuccessStatusCode) { string jsonResponse = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(jsonResponse, LauncherConstants._jsonOptions)!; - } - else { + } else { throw new Exception($"Erreur lors du changement de pseudo (Code: {response.StatusCode})"); } }