Change to static skin catalog population
This commit is contained in:
36
BlueLaminate/BlueLaminate.Scraper/Skins/CatalogSkin.cs
Normal file
36
BlueLaminate/BlueLaminate.Scraper/Skins/CatalogSkin.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace BlueLaminate.Scraper.Skins;
|
||||
|
||||
/// <summary>A single CS2 skin from the CSGO-API static catalogue (skins.json).</summary>
|
||||
/// <param name="Id">Stable catalogue id, e.g. "skin-e757fd7191f9". Globally unique natural key.</param>
|
||||
/// <param name="WeaponName">Owning weapon, e.g. "AK-47", "Hand Wraps", "Bayonet".</param>
|
||||
/// <param name="Category">Weapon category, e.g. "Rifles", "Knives", "Gloves". Becomes the weapon type.</param>
|
||||
/// <param name="Team">"CT", "T", or "Both".</param>
|
||||
/// <param name="Name">Skin/pattern name, e.g. "Dragon Lore"; "Vanilla" for knives with no finish.</param>
|
||||
/// <param name="Rarity">Rarity tier, e.g. "Covert", "Classified", "Extraordinary".</param>
|
||||
/// <param name="Description">Flavour/description text, or null.</param>
|
||||
/// <param name="ImageUrl">Catalogue image URL, or null.</param>
|
||||
/// <param name="StatTrakAvailable">True if a StatTrak variant exists.</param>
|
||||
/// <param name="SouvenirAvailable">True if a Souvenir variant exists.</param>
|
||||
/// <param name="FloatMin">Minimum wear value, or null when the catalogue gives none (e.g. vanilla knives).</param>
|
||||
/// <param name="FloatMax">Maximum wear value, or null.</param>
|
||||
/// <param name="Sources">Collections and containers this skin belongs to.</param>
|
||||
public sealed record CatalogSkin(
|
||||
string Id,
|
||||
string WeaponName,
|
||||
string Category,
|
||||
string Team,
|
||||
string Name,
|
||||
string Rarity,
|
||||
string? Description,
|
||||
string? ImageUrl,
|
||||
bool StatTrakAvailable,
|
||||
bool SouvenirAvailable,
|
||||
decimal? FloatMin,
|
||||
decimal? FloatMax,
|
||||
IReadOnlyList<CatalogSource> Sources);
|
||||
|
||||
/// <summary>A collection or container a skin originates from.</summary>
|
||||
/// <param name="Id">Stable catalogue id, e.g. "collection-set-community-37" or "crate-4288". Natural key.</param>
|
||||
/// <param name="Name">Display name, e.g. "The Dead Hand Collection", "Glove Case".</param>
|
||||
/// <param name="Type">"Collection" or "Container".</param>
|
||||
public sealed record CatalogSource(string Id, string Name, string Type);
|
||||
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