Brings up the pull-model scraper: the .NET C2 hands skin+wear jobs to Python nodriver workers that scrape cs.money and post results back, plus the supporting Core/EFCore data model, migrations, and docker-compose orchestration. IPRoyal proxying lets workers scale horizontally with a distinct residential exit IP each: every worker process mints its own sticky session at startup, and an in-process forwarding proxy injects the gateway auth so Chromium talks only to an auth-free localhost endpoint (zero CDP). On a Cloudflare challenge a worker rotates to a fresh session/IP and re-warms. Verified end-to-end against live IPRoyal: distinct US residential exits per worker and IP rotation on demand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
3.2 KiB
C#
68 lines
3.2 KiB
C#
namespace BlueLaminate.EFCore.Entities;
|
|
|
|
/// <summary>
|
|
/// One sell-order observed on cs.money via its internal
|
|
/// <c>GET /2.0/market/sell-orders</c> endpoint (scraped through the Python worker,
|
|
/// since cs.money has no public API and sits behind Cloudflare).
|
|
/// <para>
|
|
/// Kept in its own table rather than shared with the CSFloat <see cref="Listing"/>:
|
|
/// cs.money exposes a different shape (its own sell-order id, a pricing breakdown,
|
|
/// <c>quality</c>/phase, and no def/paint index). It still links to the
|
|
/// market-agnostic <see cref="SkinInstance"/> by fingerprint, so the same physical
|
|
/// item seen on both markets rolls up to one instance for cross-market analysis.
|
|
/// </para>
|
|
/// Soft-tracked across sweeps exactly like <see cref="Listing"/>:
|
|
/// <see cref="FirstSeenAt"/>/<see cref="LastSeenAt"/> bound the observation window
|
|
/// and <see cref="Status"/> flips to <see cref="ListingStatus.Removed"/> when a
|
|
/// once-seen order stops appearing (sold/delisted).
|
|
/// </summary>
|
|
public class CsMoneyListing
|
|
{
|
|
public int Id { get; set; }
|
|
|
|
/// <summary>cs.money's sell-order id (item.id). Natural key for dedup.</summary>
|
|
public long SellOrderId { get; set; }
|
|
|
|
/// <summary>
|
|
/// cs.money's asset id for the listed copy. Not a stable identity, but the
|
|
/// discriminator that distinguishes duped copies sharing one fingerprint.
|
|
/// </summary>
|
|
public string? AssetId { get; set; }
|
|
|
|
// Catalogue links. Unlike the CSFloat global sweep these are NOT best-effort:
|
|
// each scrape job targets one skin+wear, so the worker reports which Skin/
|
|
// Condition the results belong to and we set them directly.
|
|
public int SkinId { get; set; }
|
|
public Skin Skin { get; set; } = null!;
|
|
public int? ConditionId { get; set; }
|
|
public SkinCondition? Condition { get; set; }
|
|
|
|
/// <summary>The physical item (by fingerprint), shared with CSFloat listings.</summary>
|
|
public int? SkinInstanceId { get; set; }
|
|
public SkinInstance? SkinInstance { get; set; }
|
|
|
|
// Item identity, from the listing's asset block.
|
|
public string MarketHashName { get; set; } = null!;
|
|
public string? Quality { get; set; } // cs.money wear short code: fn/mw/ft/ww/bs
|
|
public decimal? FloatValue { get; set; } // null for non-skin items
|
|
public int? PaintSeed { get; set; } // asset.pattern
|
|
public string? Phase { get; set; } // doppler phase (sapphire/ruby/…)
|
|
public bool IsStatTrak { get; set; }
|
|
public bool IsSouvenir { get; set; }
|
|
public int StickerCount { get; set; }
|
|
|
|
// Pricing. cs.money returns a breakdown; Price is the actual asking price.
|
|
public decimal Price { get; set; } // pricing.default
|
|
public decimal? PriceBeforeDiscount { get; set; }
|
|
public decimal? ComputedPrice { get; set; } // pricing.computed (reference price)
|
|
public string Currency { get; set; } = "USD"; // cs.money returns no currency field
|
|
|
|
public string? InspectLink { get; set; }
|
|
|
|
// Soft-tracking across sweeps.
|
|
public DateTimeOffset FirstSeenAt { get; set; }
|
|
public DateTimeOffset LastSeenAt { get; set; }
|
|
public ListingStatus Status { get; set; }
|
|
public DateTimeOffset? RemovedAt { get; set; }
|
|
}
|