70 lines
2.8 KiB
C#
70 lines
2.8 KiB
C#
using System.Text;
|
||
|
||
namespace BlueLaminate.Core.SkinLand;
|
||
|
||
/// <summary>
|
||
/// Builds a skin.land market URL from the catalogue's weapon + skin + wear. skin.land's
|
||
/// market routes are <c>/market/csgo/{slug}/</c> where the slug is simply
|
||
/// <c>{weapon}-{skin}-{wear}</c> kebab-cased — verified against the live site (e.g.
|
||
/// "M4A4" + "Global Offensive" + "Battle-Scarred" → <c>m4a4-global-offensive-battle-scarred</c>,
|
||
/// "AK-47" + "Redline" + "Field-Tested" → <c>ak-47-redline-field-tested</c>). No discovery
|
||
/// or stored mapping is needed.
|
||
/// <para>
|
||
/// StatTrak and Souvenir are <em>separate</em> pages on skin.land (<c>stattrak-</c>/
|
||
/// <c>souvenir-</c> prefixed slugs); this builds the base (non-special) page, which is the
|
||
/// unit v1 sweeps per <c>SkinCondition</c>.
|
||
/// </para>
|
||
/// </summary>
|
||
public static class SkinLandSlug
|
||
{
|
||
private const string MarketBase = "https://skin.land/market/csgo/";
|
||
|
||
/// <summary>"M4A4", "Global Offensive", "Battle-Scarred" → the full market URL.</summary>
|
||
public static string MarketUrl(string weapon, string skinName, string condition) =>
|
||
$"{MarketBase}{Slugify($"{weapon} {skinName} {condition}")}/";
|
||
|
||
/// <summary>
|
||
/// Lowercase, collapse every run of non-alphanumeric characters to a single hyphen,
|
||
/// and trim leading/trailing hyphens. So "AK-47 | Redline (Field-Tested)" and the
|
||
/// catalogue's "AK-47 Redline Field-Tested" both reduce to "ak-47-redline-field-tested".
|
||
/// <para>
|
||
/// The apostrophe is the one exception: skin.land keeps it literally in the slug rather
|
||
/// than collapsing it to a hyphen (verified live — "AWP | Man-o'-war" →
|
||
/// <c>awp-man-o'-war</c>, "AUG | Lil' Pig" → <c>aug-lil'-pig</c>; the collapsed
|
||
/// <c>man-o-war</c>/<c>lil-pig</c> forms 404). Both the ASCII (') and typographic (’)
|
||
/// apostrophe normalize to a literal ASCII apostrophe in the slug.
|
||
/// </para>
|
||
/// </summary>
|
||
public static string Slugify(string value)
|
||
{
|
||
var sb = new StringBuilder(value.Length);
|
||
var pendingHyphen = false;
|
||
foreach (var ch in value)
|
||
{
|
||
if (ch is '\'' or '’')
|
||
{
|
||
// skin.land preserves the apostrophe as part of the word — emit it literally,
|
||
// and don't let it trigger a hyphen on either side.
|
||
sb.Append('\'');
|
||
pendingHyphen = false;
|
||
}
|
||
else if (char.IsLetterOrDigit(ch))
|
||
{
|
||
if (pendingHyphen && sb.Length > 0)
|
||
{
|
||
sb.Append('-');
|
||
}
|
||
|
||
sb.Append(char.ToLowerInvariant(ch));
|
||
pendingHyphen = false;
|
||
}
|
||
else
|
||
{
|
||
pendingHyphen = true;
|
||
}
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
}
|