Change to static skin catalog population
This commit is contained in:
105
BlueLaminate/BlueLaminate.Scraper/Skins/SkinCatalogClient.cs
Normal file
105
BlueLaminate/BlueLaminate.Scraper/Skins/SkinCatalogClient.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BlueLaminate.Scraper.Skins;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the CS2 skin catalogue from the ByMykel/CSGO-API static dataset
|
||||
/// (skins.json) and maps it to <see cref="CatalogSkin"/> records. This replaces
|
||||
/// the old HTML scraper: one JSON file carries every skin with its weapon,
|
||||
/// category, rarity, wear range, and the collections/containers it comes from.
|
||||
/// </summary>
|
||||
public sealed class SkinCatalogClient
|
||||
{
|
||||
public const string DefaultUrl =
|
||||
"https://raw.githubusercontent.com/ByMykel/CSGO-API/refs/heads/main/public/api/en/skins.json";
|
||||
|
||||
private static readonly JsonSerializerOptions Options = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
};
|
||||
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _url;
|
||||
|
||||
public SkinCatalogClient(HttpClient http, string? url = null)
|
||||
{
|
||||
_http = http;
|
||||
_url = url ?? DefaultUrl;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<CatalogSkin>> FetchAsync(CancellationToken ct = default)
|
||||
{
|
||||
await using var stream = await _http.GetStreamAsync(_url, ct);
|
||||
var dtos = await JsonSerializer.DeserializeAsync<List<SkinDto>>(stream, Options, ct)
|
||||
?? throw new InvalidOperationException("skins.json deserialized to null.");
|
||||
|
||||
return dtos.Select(Map).ToList();
|
||||
}
|
||||
|
||||
private static CatalogSkin Map(SkinDto dto)
|
||||
{
|
||||
var sources = new List<CatalogSource>();
|
||||
AddSources(sources, dto.Collections, "Collection");
|
||||
AddSources(sources, dto.Crates, "Container");
|
||||
|
||||
return new CatalogSkin(
|
||||
Id: dto.Id,
|
||||
WeaponName: dto.Weapon?.Name ?? "Unknown",
|
||||
Category: dto.Category?.Name ?? "Unknown",
|
||||
Team: MapTeam(dto.Team?.Id),
|
||||
// Knives with no finish carry a null pattern; "Vanilla" is the community term.
|
||||
Name: dto.Pattern?.Name ?? "Vanilla",
|
||||
Rarity: dto.Rarity?.Name ?? "Unknown",
|
||||
Description: dto.Description,
|
||||
ImageUrl: dto.Image,
|
||||
StatTrakAvailable: dto.Stattrak,
|
||||
SouvenirAvailable: dto.Souvenir,
|
||||
FloatMin: dto.MinFloat,
|
||||
FloatMax: dto.MaxFloat,
|
||||
Sources: sources);
|
||||
}
|
||||
|
||||
private static void AddSources(List<CatalogSource> into, List<NamedDto>? items, string type)
|
||||
{
|
||||
if (items is null)
|
||||
return;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.Id) || string.IsNullOrEmpty(item.Name))
|
||||
continue;
|
||||
if (into.Any(s => s.Id == item.Id))
|
||||
continue;
|
||||
into.Add(new CatalogSource(item.Id, item.Name, type));
|
||||
}
|
||||
}
|
||||
|
||||
private static string MapTeam(string? teamId) => teamId switch
|
||||
{
|
||||
"terrorists" => "T",
|
||||
"counter-terrorists" => "CT",
|
||||
_ => "Both",
|
||||
};
|
||||
|
||||
private sealed record SkinDto(
|
||||
string Id,
|
||||
string? Name,
|
||||
string? Description,
|
||||
NamedDto? Weapon,
|
||||
NamedDto? Category,
|
||||
NamedDto? Pattern,
|
||||
decimal? MinFloat,
|
||||
decimal? MaxFloat,
|
||||
NamedDto? Rarity,
|
||||
bool Stattrak,
|
||||
bool Souvenir,
|
||||
string? Image,
|
||||
NamedDto? Team,
|
||||
List<NamedDto>? Collections,
|
||||
List<NamedDto>? Crates);
|
||||
|
||||
private sealed record NamedDto(string? Id, string? Name);
|
||||
}
|
||||
Reference in New Issue
Block a user