diff --git a/MojangAPI/Cache/ICacheManager.cs b/MojangAPI/Cache/ICacheManager.cs deleted file mode 100644 index 79b2d1d..0000000 --- a/MojangAPI/Cache/ICacheManager.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MojangAPI.Cache -{ - public interface ICacheManager - { - T GetDefaultObject(); - T ReadCache(); - void SaveCache(T obj); - } -} diff --git a/MojangAPI/Cache/JsonFileCacheManager.cs b/MojangAPI/Cache/JsonFileCacheManager.cs deleted file mode 100644 index 6a3ed66..0000000 --- a/MojangAPI/Cache/JsonFileCacheManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.IO; -using System.Text.Json; - -namespace MojangAPI.Cache -{ - public class JsonFileCacheManager : ICacheManager where T : new() - { - public string CacheFilePath { get; private set; } - - public JsonFileCacheManager(string filepath) - { - this.CacheFilePath = filepath; - } - - public virtual T GetDefaultObject() => new T(); - - public T ReadCache() - { - if (!File.Exists(CacheFilePath)) - return GetDefaultObject(); - - try - { - string filecontent = File.ReadAllText(CacheFilePath); - return JsonSerializer.Deserialize(filecontent) ?? new T(); - } - catch - { - return GetDefaultObject(); - } - } - - public void SaveCache(T obj) - { - var dirPath = Path.GetDirectoryName(CacheFilePath); - if (!string.IsNullOrEmpty(dirPath)) - Directory.CreateDirectory(dirPath); - File.WriteAllText(CacheFilePath, JsonSerializer.Serialize(obj)); - } - } -} diff --git a/MojangAPI/Cache/SessionFileCacheManager.cs b/MojangAPI/Cache/SessionFileCacheManager.cs deleted file mode 100644 index ec2e7b5..0000000 --- a/MojangAPI/Cache/SessionFileCacheManager.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MojangAPI.Model; -using System; - -namespace MojangAPI.Cache -{ - public class SessionFileCacheManager : JsonFileCacheManager - { - public SessionFileCacheManager(string filepath) : base (filepath) - { - - } - - public override Session GetDefaultObject() - { - return new Session - { - ClientToken = Guid.NewGuid().ToString() - }; - } - } -} diff --git a/MojangAPI/HttpAction/HttpResponseHandlers.cs b/MojangAPI/HttpAction/HttpResponseHandlers.cs index 96dd998..d9352da 100644 --- a/MojangAPI/HttpAction/HttpResponseHandlers.cs +++ b/MojangAPI/HttpAction/HttpResponseHandlers.cs @@ -1,4 +1,6 @@ -using System; +using MojangAPI.Serialization; +using System.Text.Json.Serialization.Metadata; +using System; using System.IO; using System.Linq; using System.Net.Http; @@ -11,16 +13,19 @@ public static class HttpResponseHandlers { public static Func> GetDefaultResponseHandler() { - if (typeof(T) == typeof(bool)) - return GetSuccessCodeResponseHandler() as Func>; - if (typeof(T) == typeof(int)) - return GetStatusCodeResponseHandler() as Func>; - if (typeof(T) == typeof(string)) - return GetStringResponseHandler() as Func>; - if (typeof(T) == typeof(Stream)) - return GetStreamResponseHandler() as Func>; - - return GetJsonHandler(); + if (GetSuccessCodeResponseHandler() is Func> successHandler) + return successHandler; + + if (GetStatusCodeResponseHandler() is Func> statusCodeHandler) + return statusCodeHandler; + + if (GetStringResponseHandler() is Func> stringHandler) + return stringHandler; + + if (GetStreamResponseHandler() is Func> streamHandler) + return streamHandler; + + return response => GetJsonHandler(MojangJson.GetTypeInfo())(response); } public static Func> GetSuccessCodeResponseHandler() => @@ -63,17 +68,16 @@ public static Func> GetSuccessCodeResponseHandler> GetJsonErrorHandler(JsonTypeInfo typeInfo) where T : new() => + GetJsonErrorHandler(new T(), typeInfo); - public static Func> GetJsonErrorHandler() where T : new() => GetJsonErrorHandler(new T()); + public static Func> GetJsonErrorHandler(T defaultObj, JsonTypeInfo typeInfo) + { + if (typeInfo == null) + throw new ArgumentNullException(nameof(typeInfo)); - public static Func> GetJsonErrorHandler(T defaultObj) => - async (response, ex) => + return async (response, ex) => { - // if (ex == null) { try @@ -81,33 +85,42 @@ public static Func> GetSuccessCodeResponseHandler(resBody, jsonSerializerOptions); + var obj = JsonSerializer.Deserialize(resBody, typeInfo); if (obj != null) return obj; } } catch { - // ignore exception to execute next error handler + // If the response cannot be parsed, fall back to the HTTP status and original exception. } } response.EnsureSuccessStatusCode(); if (ex != null) throw ex; + return defaultObj; }; + } - public static Func> GetJsonHandler() => - async (response) => + public static Func> GetJsonHandler(JsonTypeInfo typeInfo) + { + if (typeInfo == null) + throw new ArgumentNullException(nameof(typeInfo)); + + return async response => { string res = await response.Content.ReadAsStringAsync(); if (string.IsNullOrEmpty(res)) res = "{}"; - return JsonSerializer.Deserialize(res, jsonSerializerOptions); + + return JsonSerializer.Deserialize(res, typeInfo) + ?? throw new JsonException("The response must contain a non-null JSON value."); }; + } - public static Func> GetDefaultErrorHandler() => + public static Func> GetDefaultErrorHandler() => (response, ex) => { response.EnsureSuccessStatusCode(); @@ -117,8 +130,12 @@ public static Func> GetJsonHandler() => return Task.FromResult(default(T)); }; - public static Func> GetJsonArrayHandler() => - async (response) => + public static Func> GetJsonArrayHandler(JsonTypeInfo typeInfo) + { + if (typeInfo == null) + throw new ArgumentNullException(nameof(typeInfo)); + + return async response => { string res = await response.Content.ReadAsStringAsync(); if (string.IsNullOrEmpty(res)) @@ -128,8 +145,10 @@ public static Func> GetJsonArrayHandler() => var root = doc.RootElement; return root.EnumerateArray() - .Select(elem => elem.Deserialize(jsonSerializerOptions)) + .Select(elem => JsonSerializer.Deserialize(elem.GetRawText(), typeInfo) + ?? throw new JsonException("The response array must not contain null elements.")) .ToArray(); }; + } } } diff --git a/MojangAPI/HttpAction/JsonHttpContent.cs b/MojangAPI/HttpAction/JsonHttpContent.cs index 3127328..4ad53d3 100644 --- a/MojangAPI/HttpAction/JsonHttpContent.cs +++ b/MojangAPI/HttpAction/JsonHttpContent.cs @@ -1,6 +1,7 @@ -using System.Net.Http; +using System.Net.Http; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; namespace HttpAction { @@ -8,30 +9,32 @@ public class JsonHttpContent : StringContent { private const string DefaultMediaType = "application/json"; - public JsonHttpContent(object obj) - : this(obj, null) + public JsonHttpContent(object? obj, JsonTypeInfo typeInfo) + : this(obj, typeInfo, null) { - } - public JsonHttpContent(object obj, Encoding? encoding) - : this(obj, encoding, DefaultMediaType) + public JsonHttpContent(object? obj, JsonTypeInfo typeInfo, Encoding? encoding) + : this( + obj, + typeInfo, + encoding, + DefaultMediaType) { - } - public JsonHttpContent(object obj, Encoding? encoding, string mediaType) - : base(serialize(obj), encoding, mediaType) + public JsonHttpContent( + object? obj, + JsonTypeInfo typeInfo, + Encoding? encoding, + string mediaType) + : base(serialize(obj, typeInfo), encoding, mediaType) { - } - public static string serialize(object obj) + public static string serialize(object? obj, JsonTypeInfo typeInfo) { - return JsonSerializer.Serialize(obj, new JsonSerializerOptions - { - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull - }); + return JsonSerializer.Serialize(obj, typeInfo); } } } diff --git a/MojangAPI/Mojang.cs b/MojangAPI/Mojang.cs index 2e4b2c6..a4dc3a8 100644 --- a/MojangAPI/Mojang.cs +++ b/MojangAPI/Mojang.cs @@ -1,4 +1,5 @@ -using HttpAction; +using MojangAPI.Serialization; +using HttpAction; using MojangAPI.Model; using System; using System.IO; @@ -61,7 +62,7 @@ public Task GetNameHistories(string uuid) => Path = $"user/profiles/{uuid?.Replace("-", "") ?? throw new ArgumentNullException(nameof(uuid))}/names", ResponseHandler = async (response) => { - var handler = HttpResponseHandlers.GetJsonArrayHandler(); + var handler = HttpResponseHandlers.GetJsonArrayHandler(MojangJson.GetTypeInfo()); return await handler.Invoke(response); }, ErrorHandler = MojangException.GetMojangErrorHandler() @@ -73,8 +74,8 @@ public Task GetUUIDs(string[] usernames) => Method = HttpMethod.Post, Host = "https://api.mojang.com", Path = "profiles/minecraft", - Content = new JsonHttpContent(usernames ?? throw new ArgumentNullException()), - ResponseHandler = HttpResponseHandlers.GetJsonArrayHandler(), + Content = new JsonHttpContent(usernames ?? throw new ArgumentNullException(), MojangJson.GetTypeInfo()), + ResponseHandler = HttpResponseHandlers.GetJsonArrayHandler(MojangJson.GetTypeInfo()), ErrorHandler = MojangException.GetMojangErrorHandler() }); @@ -94,15 +95,16 @@ private async Task profileResponseHandlerFromUUID(HttpResponseMes using var document = JsonDocument.Parse(responseContent); var root = document.RootElement; - var profile = root.Deserialize() ?? new PlayerProfile(); + var profile = JsonSerializer.Deserialize(root.GetRawText(), MojangJson.GetTypeInfo()) ?? new PlayerProfile(); try { var innerValue = root.GetProperty("properties") - .EnumerateArray() - .First() - .GetProperty("value") - .GetString(); + .EnumerateArray() + .First() + .GetProperty("value") + .GetString() + ?? throw new JsonException("The profile texture value must be a non-null string."); var decoded = Convert.FromBase64String(innerValue); using var skinDocument = JsonDocument.Parse(decoded); @@ -117,10 +119,10 @@ private async Task profileResponseHandlerFromUUID(HttpResponseMes catch { SkinType? defaultSkinType = null; - if (string.IsNullOrEmpty(profile?.UUID)) - defaultSkinType = Skin.GetDefaultSkinType(profile!.UUID!); + if (profile.UUID is string profileUuid && !string.IsNullOrEmpty(profileUuid)) + defaultSkinType = Skin.GetDefaultSkinType(profileUuid); - profile!.Skin = new Skin(null, defaultSkinType); + profile.Skin = new Skin(null, defaultSkinType); } return profile; @@ -148,7 +150,7 @@ private async Task profileResponseHandlerFromAccessToken(HttpResp using var doc = JsonDocument.Parse(responseContent); var root = doc.RootElement; - var profile = root.Deserialize() ?? new PlayerProfile(); + var profile = JsonSerializer.Deserialize(root.GetRawText(), MojangJson.GetTypeInfo()) ?? new PlayerProfile(); try { @@ -160,10 +162,10 @@ private async Task profileResponseHandlerFromAccessToken(HttpResp catch { SkinType? defaultSkinType = null; - if (string.IsNullOrEmpty(profile?.UUID)) - defaultSkinType = Skin.GetDefaultSkinType(profile!.UUID!); + if (profile.UUID is string profileUuid && !string.IsNullOrEmpty(profileUuid)) + defaultSkinType = Skin.GetDefaultSkinType(profileUuid); - profile!.Skin = new Skin(null, defaultSkinType); + profile.Skin = new Skin(null, defaultSkinType); } return profile; @@ -181,7 +183,7 @@ public Task GetPlayerAttributes(string accessToken) => { "Authorization", "Bearer " + accessToken } }, - ResponseHandler = HttpResponseHandlers.GetJsonHandler(), + ResponseHandler = HttpResponseHandlers.GetJsonHandler(MojangJson.GetTypeInfo()), ErrorHandler = MojangException.GetMojangErrorHandler() }); @@ -222,7 +224,7 @@ public Task GetPlayerCertificates(string accessToken) => { "Authorization", "Bearer " + accessToken } }, - ResponseHandler = HttpResponseHandlers.GetJsonHandler(), + ResponseHandler = HttpResponseHandlers.GetJsonHandler(MojangJson.GetTypeInfo()), ErrorHandler = MojangException.GetMojangErrorHandler() }); @@ -282,11 +284,11 @@ public Task ChangeSkin(string uuid, string accessToken, SkinType { "Authorization", "Bearer " + accessToken } }, - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new SkinRequest { - variant = skinType.GetModelType(), - url = skinUrl - }), + Variant = skinType.GetModelType(), + Url = skinUrl + }, MojangJson.GetTypeInfo()), CheckValidation = (h) => { @@ -399,12 +401,12 @@ public Task GetStatistics(string[] options) => Host = "https://api.mojang.com", Path = "orders/statistics", - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new StatisticsRequest { - metricKeys = options - }), + MetricKeys = options + }, MojangJson.GetTypeInfo()), - ResponseHandler = HttpResponseHandlers.GetJsonHandler(), + ResponseHandler = HttpResponseHandlers.GetJsonHandler(MojangJson.GetTypeInfo()), ErrorHandler = MojangException.GetMojangErrorHandler() }); diff --git a/MojangAPI/MojangAPI.csproj b/MojangAPI/MojangAPI.csproj index 0212f76..757d486 100644 --- a/MojangAPI/MojangAPI.csproj +++ b/MojangAPI/MojangAPI.csproj @@ -1,9 +1,9 @@  - netstandard2.0 + netstandard2.0;net10.0 enable - 8.0 + 10.0 true .NET Library for Mojang API, Mojang Authentication and Microsoft Xbox Authentication MIT @@ -12,11 +12,16 @@ mojang minecraft api + + true + + - - all - - - + + + + + + diff --git a/MojangAPI/MojangAuth.cs b/MojangAPI/MojangAuth.cs index 9de7ebb..0d0d868 100644 --- a/MojangAPI/MojangAuth.cs +++ b/MojangAPI/MojangAuth.cs @@ -1,5 +1,5 @@ -using HttpAction; -using MojangAPI.Cache; +using MojangAPI.Serialization; +using HttpAction; using MojangAPI.Model; using System; using System.Net.Http; @@ -11,53 +11,25 @@ namespace MojangAPI public class MojangAuth { private readonly HttpClient client; - private readonly ICacheManager? cacheManager; - public MojangAuth() : this(null) { } - - public MojangAuth(HttpClient? client) : - this(client, new SessionFileCacheManager("mojang_auth.json")) - { } - - public MojangAuth(HttpClient? httpClient, ICacheManager? _cacheManager) + public MojangAuth() : this(null) { - if (httpClient == null) - this.client = Mojang.DefaultClient.Value; - else - this.client = httpClient; - - this.cacheManager = _cacheManager; } - private string CreateClientToken() + public MojangAuth(HttpClient? client) { - return Guid.NewGuid().ToString(); - } - - private void SaveSession(Session session) - { - if (string.IsNullOrEmpty(session.ClientToken)) - { - Session? cachedSession = cacheManager?.ReadCache(); - if (cachedSession == null || string.IsNullOrEmpty(cachedSession.ClientToken)) - session.ClientToken = CreateClientToken(); - else - session.ClientToken = cachedSession.ClientToken; - } - cacheManager?.SaveCache(session); + if (client == null) + this.client = Mojang.DefaultClient.Value; + else + this.client = client; } - private async Task Request(HttpAction httpAction) where T : MojangAuthResponse + private Task Request(HttpAction httpAction) where T : MojangAuthResponse { if (string.IsNullOrEmpty(httpAction.Host)) httpAction.Host = "https://authserver.mojang.com"; - T response = await client.SendActionAsync(httpAction); - - if (response.IsSuccess && response.Session != null) - SaveSession(response.Session); - - return response; + return client.SendActionAsync(httpAction); } private async Task authResponseHandler(HttpResponseMessage response) @@ -69,11 +41,16 @@ private async Task authResponseHandler(HttpResponseMessage r if (!root.TryGetProperty("selectedProfile", out var selectedProfile)) return new MojangAuthResponse(MojangAuthResult.NoProfile); + string? clientToken = null; + if (root.TryGetProperty("clientToken", out var clientTokenProperty)) + clientToken = clientTokenProperty.GetString(); + return new MojangAuthResponse(MojangAuthResult.Success) { Session = new Session { AccessToken = root.GetProperty("accessToken").GetString(), + ClientToken = clientToken, UUID = selectedProfile.GetProperty("id").GetString(), Username = selectedProfile.GetProperty("name").GetString() } @@ -88,7 +65,7 @@ private async Task errorResponseHandler(HttpResponseMessage? try { string content = await response.Content.ReadAsStringAsync(); - MojangAuthResponse authResponse = JsonSerializer.Deserialize(content) + MojangAuthResponse authResponse = JsonSerializer.Deserialize(content, MojangJson.GetTypeInfo()) ?? new MojangAuthResponse(MojangAuthResult.UnknownError); switch (authResponse.Error) @@ -117,7 +94,7 @@ private async Task errorResponseHandler(HttpResponseMessage? } public Task Authenticate(string email, string password) => - Authenticate(email, password, cacheManager?.ReadCache()?.ClientToken); + Authenticate(email, password, Guid.NewGuid().ToString()); public Task Authenticate(string email, string password, string? clientToken) => Request(new HttpAction @@ -125,17 +102,12 @@ public Task Authenticate(string email, string password, stri Method = HttpMethod.Post, Path = "authenticate", - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new AuthenticationRequest { - agent = new - { - name = "Minecraft", - version = 1 - }, - username = email, - password, - clientToken - }), + Username = email, + Password = password, + ClientToken = clientToken + }, MojangJson.GetTypeInfo()), CheckValidation = (h) => { @@ -147,15 +119,6 @@ public Task Authenticate(string email, string password, stri ErrorHandler = errorResponseHandler }); - public async Task TryAutoLogin() - { - Session? cachedSession = cacheManager?.ReadCache(); - if (cachedSession == null || cachedSession.IsEmpty) - return new MojangAuthResponse(MojangAuthResult.InvalidSession); - else - return await TryAutoLogin(cachedSession); - } - public async Task TryAutoLogin(Session session) { if (session.IsEmpty) @@ -168,32 +131,23 @@ public async Task TryAutoLogin(Session session) return res; } - public Task Refresh() - { - Session? cachedSession = cacheManager?.ReadCache(); - if (cachedSession == null) - return Task.FromResult(new MojangAuthResponse(MojangAuthResult.InvalidSession)); - else - return Refresh(cachedSession); - } - public Task Refresh(Session session) => Request(new HttpAction { Method = HttpMethod.Post, Path = "refresh", - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new TokenRequest { - accessToken = session.AccessToken, - clientToken = session.ClientToken, + AccessToken = session.AccessToken, + ClientToken = session.ClientToken, - selectedProfile = new + SelectedProfile = new SelectedProfile { - id = session.UUID, - name = session.Username + Id = session.UUID, + Name = session.Username } - }), + }, MojangJson.GetTypeInfo()), CheckValidation = (h) => { @@ -205,15 +159,6 @@ public Task Refresh(Session session) => ErrorHandler = errorResponseHandler }); - public Task Validate() - { - Session? cachedSession = cacheManager?.ReadCache(); - if (cachedSession == null || string.IsNullOrEmpty(cachedSession.AccessToken)) - return Task.FromResult(new MojangAuthResponse(MojangAuthResult.InvalidSession)); - else - return Validate(cachedSession); - } - public async Task Validate(Session session) { if (session.AccessToken == null) @@ -231,11 +176,11 @@ public Task Validate(string accessToken, string? clientToken Method = HttpMethod.Post, Path = "validate", - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new TokenRequest { - accessToken, - clientToken - }), + AccessToken = accessToken, + ClientToken = clientToken + }, MojangJson.GetTypeInfo()), CheckValidation = (h) => { @@ -253,11 +198,11 @@ public Task Signout(string email, string password) => Method = HttpMethod.Post, Path = "signout", - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new CredentialsRequest { - username = email, - password = password - }), + Username = email, + Password = password + }, MojangJson.GetTypeInfo()), CheckValidation = (h) => { @@ -270,26 +215,17 @@ public Task Signout(string email, string password) => ErrorHandler = errorResponseHandler }); - public Task Invalidate() - { - Session? cachedSession = cacheManager?.ReadCache(); - if (cachedSession == null || cachedSession.AccessToken == null) - return Task.FromResult(new MojangAuthResponse(MojangAuthResult.InvalidSession)); - else - return Invalidate(cachedSession.AccessToken, cachedSession.ClientToken); - } - public Task Invalidate(string accessToken, string? clientToken) => Request(new HttpAction { Method = HttpMethod.Post, Path = "invalidate", - Content = new JsonHttpContent(new + Content = new JsonHttpContent(new TokenRequest { - accessToken = accessToken, - clientToken = clientToken - }), + AccessToken = accessToken, + ClientToken = clientToken + }, MojangJson.GetTypeInfo()), CheckValidation = (h) => { @@ -314,7 +250,7 @@ public Task LoginWithXbox(string uhs, string xstsToken) = ResponseHandler = async (response) => { string content = await response.Content.ReadAsStringAsync(); - MicrosoftAuthResponse authResponse = JsonSerializer.Deserialize(content) + MicrosoftAuthResponse authResponse = JsonSerializer.Deserialize(content, MojangJson.GetTypeInfo()) ?? new MicrosoftAuthResponse(); authResponse.ExpiresOn = DateTime.Now.AddSeconds(authResponse.ExpiresIn); return authResponse; @@ -351,8 +287,6 @@ public async Task RequestSessionWithXbox(string uhs, string UUID = profile.UUID }; - SaveSession(session); - return new MojangAuthResponse(MojangAuthResult.Success) { Session = session diff --git a/MojangAPI/MojangException.cs b/MojangAPI/MojangException.cs index 34d1540..a50952a 100644 --- a/MojangAPI/MojangException.cs +++ b/MojangAPI/MojangException.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Http; @@ -62,7 +62,7 @@ public static MojangException ParseFromResponse(string responseBody, int statusC } } - public static Func GetMojangErrorHandler() => + public static Func> GetMojangErrorHandler() => async (response, ex) => { if (ex == null) @@ -85,11 +85,7 @@ public static MojangException ParseFromResponse(string responseBody, int statusC throw new MojangException($"{(int)response.StatusCode}: {response.ReasonPhrase}", (int)response.StatusCode); }; - public static Func> GetMojangErrorHandler() => - async (response, ex) => - { - await GetMojangErrorHandler().Invoke(response, ex); - return default(T); - }; + public static Func GetMojangErrorHandler() => + (response, ex) => GetMojangErrorHandler()(response, ex); } } diff --git a/MojangAPI/SecurityQuestion/QuestionFlow.cs b/MojangAPI/SecurityQuestion/QuestionFlow.cs index 8b14089..91d8f1b 100644 --- a/MojangAPI/SecurityQuestion/QuestionFlow.cs +++ b/MojangAPI/SecurityQuestion/QuestionFlow.cs @@ -1,4 +1,5 @@ -using MojangAPI.Model; +using MojangAPI.Serialization; +using MojangAPI.Model; using System; using System.Collections.Generic; using System.Linq; @@ -86,7 +87,7 @@ public Task SendAnswers(QuestionList list, string accessToken) var jarr = new JsonArray(); foreach (var item in list) { - jarr.Add(new JsonObject + jarr.Add((JsonNode)new JsonObject { ["id"] = item.AnswerId, ["answer"] = item.Answer @@ -107,7 +108,7 @@ public Task SendAnswers(QuestionList list, string accessToken) Content = new StringContent(jarr.ToJsonString(), Encoding.UTF8, "application/json"), ResponseHandler = HttpResponseHandlers.GetSuccessCodeResponseHandler(), - ErrorHandler = HttpResponseHandlers.GetJsonErrorHandler() + ErrorHandler = HttpResponseHandlers.GetJsonErrorHandler(MojangJson.GetTypeInfo()) }); } } diff --git a/MojangAPI/Serialization/MojangJson.cs b/MojangAPI/Serialization/MojangJson.cs new file mode 100644 index 0000000..f9c4706 --- /dev/null +++ b/MojangAPI/Serialization/MojangJson.cs @@ -0,0 +1,18 @@ +using System; +using System.Text.Json.Serialization.Metadata; + +namespace MojangAPI.Serialization +{ + internal static class MojangJson + { + internal static JsonTypeInfo GetTypeInfo() + { + // Callers must supply source-generated metadata for custom types. + var typeInfo = MojangJsonContext.Default.GetTypeInfo(typeof(T)) as JsonTypeInfo; + if (typeInfo == null) + throw new NotSupportedException($"Supply a source-generated JsonTypeInfo<{typeof(T).Name}> for custom JSON types."); + + return typeInfo; + } + } +} diff --git a/MojangAPI/Serialization/MojangJsonContext.cs b/MojangAPI/Serialization/MojangJsonContext.cs new file mode 100644 index 0000000..7bde5a9 --- /dev/null +++ b/MojangAPI/Serialization/MojangJsonContext.cs @@ -0,0 +1,27 @@ +using MojangAPI.Model; +using System.Text.Json.Serialization; + +namespace MojangAPI.Serialization +{ + [JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] + [JsonSerializable(typeof(PlayerUUID))] + [JsonSerializable(typeof(PlayerUUID[]))] + [JsonSerializable(typeof(PlayerProfile))] + [JsonSerializable(typeof(NameHistory))] + [JsonSerializable(typeof(NameHistory[]))] + [JsonSerializable(typeof(PlayerAttributes))] + [JsonSerializable(typeof(PlayerCertificates))] + [JsonSerializable(typeof(Statistics))] + [JsonSerializable(typeof(MojangAuthResponse))] + [JsonSerializable(typeof(MicrosoftAuthResponse))] + [JsonSerializable(typeof(SkinRequest))] + [JsonSerializable(typeof(StatisticsRequest))] + [JsonSerializable(typeof(AuthenticationRequest))] + [JsonSerializable(typeof(TokenRequest))] + [JsonSerializable(typeof(CredentialsRequest))] + [JsonSerializable(typeof(string[]))] + [JsonSerializable(typeof(bool))] + internal partial class MojangJsonContext : JsonSerializerContext + { + } +} diff --git a/MojangAPI/Serialization/Requests.cs b/MojangAPI/Serialization/Requests.cs new file mode 100644 index 0000000..f02690a --- /dev/null +++ b/MojangAPI/Serialization/Requests.cs @@ -0,0 +1,73 @@ +using System.Text.Json.Serialization; + +namespace MojangAPI.Serialization +{ + internal sealed class SkinRequest + { + [JsonPropertyName("variant")] + public string? Variant { get; set; } + + [JsonPropertyName("url")] + public string? Url { get; set; } + } + + internal sealed class StatisticsRequest + { + [JsonPropertyName("metricKeys")] + public string[]? MetricKeys { get; set; } + } + + internal sealed class CredentialsRequest + { + [JsonPropertyName("username")] + public string? Username { get; set; } + + [JsonPropertyName("password")] + public string? Password { get; set; } + } + + internal sealed class AuthenticationRequest + { + [JsonPropertyName("agent")] + public Agent Agent { get; set; } = new Agent(); + + [JsonPropertyName("username")] + public string? Username { get; set; } + + [JsonPropertyName("password")] + public string? Password { get; set; } + + [JsonPropertyName("clientToken")] + public string? ClientToken { get; set; } + } + + internal sealed class Agent + { + [JsonPropertyName("name")] + public string Name { get; set; } = "Minecraft"; + + [JsonPropertyName("version")] + public int Version { get; set; } = 1; + } + + internal sealed class TokenRequest + { + [JsonPropertyName("accessToken")] + public string? AccessToken { get; set; } + + [JsonPropertyName("clientToken")] + public string? ClientToken { get; set; } + + [JsonPropertyName("selectedProfile")] + public SelectedProfile? SelectedProfile { get; set; } + } + + internal sealed class SelectedProfile + { + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + } +} diff --git a/MojangAPISample/TestMojangAuth.cs b/MojangAPISample/TestMojangAuth.cs index 914a690..8596b19 100644 --- a/MojangAPISample/TestMojangAuth.cs +++ b/MojangAPISample/TestMojangAuth.cs @@ -13,28 +13,13 @@ internal class TestMojangAuth { public static async Task Test(MojangAuth auth) { - MojangAuthResponse res; - - Console.WriteLine("TryAutoLogin"); - res = await auth.TryAutoLogin(); - printAuthResponse(res); - Console.WriteLine(); - - if (res.IsSuccess) - return new MSession - { - AccessToken = res.Session.AccessToken, - UUID = res.Session.UUID, - Username = res.Session.Username - }; - Console.WriteLine("Authenticate"); Console.Write("Email : "); var email = Console.ReadLine(); Console.Write("Password : "); var pw = Console.ReadLine(); - res = await auth.Authenticate(email, pw); // fill your mojang email and password + var res = await auth.Authenticate(email, pw); printAuthResponse(res); Console.WriteLine(); diff --git a/docs/MojangAuth.md b/docs/MojangAuth.md index 1d3cb62..bfdbdeb 100644 --- a/docs/MojangAuth.md +++ b/docs/MojangAuth.md @@ -15,7 +15,7 @@ You can check whether the request was successful or failed to check `IsSuccess` `Session` property is the result of authenticate. It contains `Username`, `AccessToken`, and `UUID` If `IsSuccess` is false, `Result`, `Error`, and `ErrorMessage` property tell you why the request failed. -`MojangAuth` has its own session caching manager. You don't have to type mojang email and password everytime when you login. Login once using your mojang email and password, and `MojangAuth` will save your session information into json file(not contain your raw password, it contains only tokens). Next time when you login just call `TryAutoLogin`. it authenticate you using cached session without typing mojang email and password. +`MojangAuth` does not persist sessions. Callers must keep the returned `Session` and pass it explicitly to `TryAutoLogin(session)`, `Refresh(session)`, or `Validate(session)`. There are two ways to login. One is `Yggdrasil Mojang Authentication` which is described below, and the other is `Microsoft Xbox Authentication` which is new way to login. [Microsoft Xbox Authentication](./XboxAuthentication.md) @@ -26,16 +26,7 @@ Example: HttpClient httpClient = new HttpClient(); MojangAuth auth = new MojangAuth(httpClient); -MojangAuthResponse res; - -res = await auth.TryAutoLogin(); // login using cached session -if (!res.IsSuccess) // failed to login using cached session -{ - Console.WriteLine(res.Result.ToString()); - - // it will save your login information into json file - res = await auth.Authenticate("mojang email", "password"); -} +MojangAuthResponse res = await auth.Authenticate("mojang email", "password"); Console.WriteLine(res.ToString()); @@ -49,27 +40,17 @@ Session session = res.Session; #### public MojangAuth(HttpClient client) -Same as `new MojangAuth(client, new SessionFileCacheManager("mojang_auth.json"))` - -#### public MojangAuth(HttpClient httpClient, ICacheManager\ _cacheManager) - -Initialize a new instance of the `MojangAuth` class with the specified cache manager. -If `_cacheManager` is null, `MojangAuth` does not cache session. -If you want to change cache file path, create a instance of `SessionFileCacheManager` with file path. -Example: -```csharp -MojangAuth auth = new MojangAuth(client, new SessionFileCacheManager("session_file_path.json")); -``` +Sends authentication requests using the supplied `HttpClient`, or the default client if `null` is supplied. No session files are created or read. ### Methods #### Authenticate ```csharp -// using cached client token or create new client token if it was null. +// Generate a new client token for each authentication request. MojangAuthResponse res = await auth.Authenticate("mojang email", "password"); -// using the specified client token +// Use the supplied client token. MojangAuthResponse res = await auth.Authenticate("mojang email", "password", "clientToken"); if (res.IsSuccess) @@ -87,29 +68,21 @@ else #### TryAutoLogin ```csharp -// call Validate() and Refersh() using cached session. -MojangAuthResponse res = await auth.TryAutoLogin(); +// Validate the supplied session and refresh it if validation fails. +MojangAuthResponse res = await auth.TryAutoLogin(session); ``` #### Refresh ```csharp -// using cached session -MojangAuthResponse res = await auth.Refresh(); -``` -```csharp -// using the specified session +// Pass the session or tokens managed by the caller. MojangAuthResponse res = await auth.Refresh(session); ``` #### Validate ```csharp -// using cached session -MojangAuthResponse res = await auth.Validate(); -``` -```csharp -// using the specified session +// Pass the session or tokens managed by the caller. MojangAuthResponse res = await auth.Validate(session); ``` @@ -122,11 +95,7 @@ MojangAuthResponse res = await auth.Signout("mojang email", "password"); #### Invalidate ```csharp -// using cached session -MojangAuthResponse res = await auth.Invalidate(); -``` -```csharp -// using the specified session +// Pass the session or tokens managed by the caller. MojangAuthResponse res = await auth.Invalidate("accessToken", "clientToken"); ``` -*note: 'clientToken' can be null* \ No newline at end of file +*note: 'clientToken' can be null*