using System; using System.Collections.Generic; using System.Linq; using IPA.JsonConverters; using IPA.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SemVer; using Version = SemVer.Version; namespace IPA.Updating.BeatMods { #if BeatSaber class ApiEndpoint { public const string BeatModBase = "https://beatmods.com"; public const string ApiBase = BeatModBase + "/api/v1/mod"; public const string GetModInfoEndpoint = "?name={0}&version={1}"; public const string GetModsByName = "?name={0}"; class HexArrayConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(byte[]); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { return null; } if (reader.TokenType == JsonToken.String) { try { return Utils.StringToByteArray((string)reader.Value); } catch (Exception ex) { throw new Exception($"Error parsing version string: {reader.Value}", ex); } } throw new Exception( $"Unexpected token or value when parsing hex string. Token: {reader.TokenType}, Value: {reader.Value}"); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); } else { if (!(value is byte[])) { throw new JsonSerializationException("Expected byte[] object value"); } writer.WriteValue(Utils.ByteArrayToString((byte[]) value)); } } } class ModMultiformatJsonConverter : JsonConverter { public override Mod ReadJson(JsonReader reader, Type objectType, Mod existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.String) return new Mod { Id = reader.Value as string, IsIdReference = true }; else { if (reader.TokenType != JsonToken.StartObject) return null; return serializer.Deserialize(reader); } } public override void WriteJson(JsonWriter writer, Mod value, JsonSerializer serializer) => serializer.Serialize(writer, value); } [Serializable] public class Mod { #pragma warning disable CS0649 /// /// Will be a useless string of characters. Do not use. /// [JsonProperty("_id")] public string Id; [JsonIgnore] public bool IsIdReference = false; [JsonProperty("required")] public bool Required; [JsonProperty("name")] public string Name; [JsonProperty("version"), JsonConverter(typeof(SemverVersionConverter))] public Version Version; [JsonProperty("gameVersion"), JsonConverter(typeof(JsonConverters.AlmostVersionConverter))] public AlmostVersion GameVersion; [Serializable] public class AuthorType { [JsonProperty("username")] public string Name; [JsonProperty("_id")] public string Id; public override string ToString() => Name; } [JsonProperty("author")] public AuthorType Author; [JsonProperty("status")] public string Status; public const string ApprovedStatus = "approved"; [JsonProperty("description")] public string Description; [JsonProperty("category")] public string Category; [JsonProperty("link")] public Uri Link; #pragma warning restore CS0649 [Serializable] public class DownloadsObject { public const string TypeUniversal = "universal"; public const string TypeSteam = "steam"; public const string TypeOculus = "oculus"; [JsonProperty("type")] public string Type; [JsonProperty("url")] public string Path; [Serializable] public class HashObject { [JsonProperty("hash"), JsonConverter(typeof(HexArrayConverter))] public byte[] Hash; [JsonProperty("file")] public string File; } /// /// Hashes stored are MD5 /// [JsonProperty("hashMd5")] public HashObject[] Hashes; } [JsonProperty("downloads")] public DownloadsObject[] Downloads; [JsonProperty("dependencies", ItemConverterType = typeof(ModMultiformatJsonConverter))] public Mod[] Dependencies; public override string ToString() { return $"{{\"{Name}\"v{Version} by {Author} files for {string.Join(", ", Downloads.Select(d => d.Type))}}}"; } } } #endif }