// // Copyright 2021–2022 alterNERDtive. // // This file is part of EDNA. // // EDNA is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // EDNA is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with EDNA. If not, see <https://www.gnu.org/licenses/>. // using System; using System.Collections.Generic; using System.Threading.Tasks; using RestSharp; namespace alterNERDtive.Edna.Edsm { /// /// A star system in the galaxy of Elite Dangerous, as returned from EDSM’s /// “Systems” API. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Either this or wrong class/struct order 🤷")] public struct ApiSystem { /// /// Gets or sets the distance to the reference system. Only used by the /// Sphere and Cube endpoints of the Systems API. /// public double Distance { get; set; } /// /// Gets or sets the body count of the star system. Only used by the /// Sphere and Cube endpoints of the Systems API. /// public int? BodyCount { get; set; } /// /// Gets or sets the name of the star system. /// public string Name { get; set; } /// /// Gets or sets the EDSM ID of the star system. /// public int Id { get; set; } /// /// Gets or sets the ID64 of the star system. /// public ulong Id64 { get; set; } /// /// Gets or sets the location of the star system. /// public Coordinates? Coords { get; set; } /// /// Gets or sets a value indicating whether the coordinates of the star /// system are “locked”. /// /// FIXXME: I _think_ that means they have been confirmed by multiple /// people, but since it’s undocumented I am not sure. /// public bool CoordsLocked { get; set; } /// /// Gets or sets a value indicating whether the star system requires /// acquisition of a permit. /// public bool RequirePermit { get; set; } /// /// Gets or sets the name of the permit required to visit the star system. /// public string? PermitName { get; set; } /// /// Gets or sets general information about the star system. /// public SystemInformation? Information { get; set; } /// /// Gets or sets information about the primary star of the star system. /// public PrimaryStarInformation? PrimaryStar { get; set; } /// /// Information about the primary star of a star system. /// public struct PrimaryStarInformation { /// /// Gets or sets the star’s name. /// public string Name { get; set; } /// /// Gets or sets the star’s star type (full text version). /// public string Type { get; set; } /// /// Gets or sets a value indicating whether or not the star is able /// to be scooped for fuel. /// public bool IsScoopable { get; set; } } /// /// General Information about a star system. /// public struct SystemInformation { /// /// Gets or sets the star system’s allegiance. /// /// Can currently be Federation, Empire, Alliance, Independent, Thargoid. /// public string? Allegiance { get; set; } /// /// Gets or sets the star system’s government type. /// public string? Government { get; set; } /// /// Gets or sets the star system’s current controlling faction. /// public string? Faction { get; set; } /// /// Gets or sets the star system’s current controlling faction’s state. /// public string? FactionState { get; set; } /// /// Gets or sets the star system’s current population. /// public ulong? Population { get; set; } /// /// Gets or sets the star system’s current security state. /// public string? Security { get; set; } /// /// Gets or sets the star system’s primary economy. /// public string? Economy { get; set; } /// /// Gets or sets the star system’s secondary economy. /// public string? SecondEconomy { get; set; } /// /// Gets or sets the star system’s reserve level. /// public string? Reserve { get; set; } } } /// /// The SystemsApi class is used to pull information about a single or /// multiple star systems from EDSM’s “Systems” API. /// /// See https://www.edsm.net/en/api-v1. /// public class SystemsApi { private static readonly Uri ApiUrl = new Uri("https://www.edsm.net/en/api-v1"); private static readonly RestClient ApiClient = new RestClient(ApiUrl) .AddDefaultQueryParameter("showId", "1") .AddDefaultQueryParameter("showCoordinates", "1") .AddDefaultQueryParameter("showPermit", "1") .AddDefaultQueryParameter("showInformation", "1") .AddDefaultQueryParameter("showPrimaryStar", "1"); /// /// Retrieves a single star system by name. /// /// The system name. /// The star system. public static async Task FindSystem(string name) { RestResponse response = await ApiClient.ExecuteAsync( new RestRequest("system").AddQueryParameter("systemName", name)); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"System “{name}” not found.", nameof(name)); } return response.Data; } /// /// Retrieves multiple star systems by partial name. /// /// The partial name. /// A list of star systems beginning with the partial name. public static async Task> FindSystems(string name) { RestResponse> response = await ApiClient.ExecuteAsync>( new RestRequest("systems").AddQueryParameter("systemName", name)); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"No system found for partial name “{name}”.", nameof(name)); } return response.Data; } /// /// Retrieves the star systems to a given set of system names. /// /// The star systems in question. /// A representing the result of the asynchronous operation. public static async Task> FindSystems(IEnumerable names) { RestRequest request = new RestRequest("systems"); foreach (string name in names) { request.AddQueryParameter("systemName[]", name); } RestResponse> response = await ApiClient.ExecuteAsync>(request); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"No systems found for “{string.Join(", ", names)}”.", nameof(names)); } return response.Data; } /// /// Retrieves all star systems contained in a sphere around a given star /// system. /// /// The name of the star system. /// The minimum radius within which star systems are /// excluded from the result set. /// The radius of the search sphere. /// A representing the result of the asynchronous operation. public static async Task> FindSystemsSphere(string name, int minRadius = 0, int radius = 50) { int max = 100; if (radius > max) { throw new ArgumentException($"Radius cannot exceed {max} ly.", nameof(radius)); } RestResponse> response = await ApiClient.ExecuteAsync>( new RestRequest("sphere-systems") .AddQueryParameter("systemName", name) .AddQueryParameter("minRadius", minRadius) .AddQueryParameter("radius", radius)); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"No systems found within {radius} ly of “{name}”."); } return response.Data; } /// /// Retrieves all star systems contained in a sphere around given coordinates. /// /// The coordinates in question. /// The minimum radius within which star systems /// are excluded from the result set. /// The radius of the search sphere. /// A representing the result of the asynchronous operation. public static async Task> FindSystemsSphere(Coordinates coords, int minRadius = 0, int radius = 50) { int max = 100; if (radius > max) { throw new ArgumentException($"Radius cannot exceed {max} ly.", nameof(radius)); } RestResponse> response = await ApiClient.ExecuteAsync>( new RestRequest("sphere-systems") .AddQueryParameter("x", coords.X) .AddQueryParameter("y", coords.Y) .AddQueryParameter("z", coords.Z) .AddQueryParameter("minRadius", minRadius) .AddQueryParameter("radius", radius)); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"No systems found within {radius} ly of {coords}."); } return response.Data!; } /// /// Retrivese all star systems contained in a cube centered on a given /// star system. /// /// The name of the system. /// The boundary size for the cube. /// A representing the result of the asynchronous operation. public static async Task> FindSystemsCube(string name, int boundarySize = 100) { int max = 200; if (boundarySize > max) { throw new ArgumentException($"Boundary size cannot exceed {max} ly.", nameof(boundarySize)); } RestResponse> response = await ApiClient.ExecuteAsync>( new RestRequest("cube-systems") .AddQueryParameter("systemName", name) .AddQueryParameter("size", boundarySize)); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"No systems found in a cube of {boundarySize} ly boundary size around “{name}”."); } return response.Data!; } /// /// Retrieves all star systems contained in a cube centered on a given /// set of coordinates. /// /// The coordinates in question. /// The boundary size for the cube. /// A representing the result of the asynchronous operation. public static async Task> FindSystemsCube(Coordinates coords, int boundarySize = 100) { int max = 200; if (boundarySize > max) { throw new ArgumentException($"Boundary size cannot exceed {max} ly.", nameof(boundarySize)); } RestResponse> response = await ApiClient.ExecuteAsync>( new RestRequest("cube-systems") .AddQueryParameter("x", coords.X) .AddQueryParameter("y", coords.Y) .AddQueryParameter("z", coords.Z) .AddQueryParameter("size", boundarySize)); try { CheckResponseStatus(response); } catch (ArgumentException) { throw new ArgumentException($"No systems found in a cube of {boundarySize} ly boundary size around {coords}."); } return response.Data; } private static void CheckResponseStatus(RestResponse response) { if (response.ResponseStatus != ResponseStatus.Completed) { if (response.ErrorException is System.Text.Json.JsonException && response.Content!.Equals("[]")) { throw new ArgumentException(); } else { throw response.ErrorException!; } } } } }