Files
Operation-Blue-Laminate-v2/BlueLaminate/BlueLaminate.Core/CsMoney/MarketPresenceService.cs
bob dc7c3f99ae Add cs.money worker stack with per-worker IPRoyal residential proxy
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>
2026-05-31 15:03:53 -05:00

47 lines
2.2 KiB
C#

using BlueLaminate.EFCore.Data;
using Microsoft.EntityFrameworkCore;
namespace BlueLaminate.Core.CsMoney;
/// <summary>One marketplace's current presence for a skin or a physical item.</summary>
/// <param name="Marketplace">"csfloat", "csmoney", …</param>
/// <param name="ActiveCount">Active listings on this market.</param>
/// <param name="MinPrice">Cheapest active listing (the comparable price).</param>
/// <param name="MaxPrice">Dearest active listing.</param>
/// <param name="LastSeenAt">When this market was last observed to have it.</param>
public sealed record MarketPresence(
string Marketplace, int ActiveCount, decimal MinPrice, decimal MaxPrice, DateTimeOffset LastSeenAt);
/// <summary>
/// Answers "where is this listed?" over the cross-market <c>market_listings</c> view.
/// Per physical item (<see cref="ForInstanceAsync"/>) for the exact-copy / arbitrage /
/// dupe view, or per catalogue skin (<see cref="ForSkinAsync"/>) for "which markets
/// carry this skin, and cheapest where".
/// </summary>
public sealed class MarketPresenceService
{
private const string Active = "Active";
private readonly SkinTrackerDbContext _db;
public MarketPresenceService(SkinTrackerDbContext db) => _db = db;
/// <summary>Markets currently listing this exact physical copy.</summary>
public Task<List<MarketPresence>> ForInstanceAsync(int skinInstanceId, CancellationToken ct = default) =>
_db.MarketListings
.Where(m => m.SkinInstanceId == skinInstanceId && m.Status == Active)
.GroupBy(m => m.Marketplace)
.Select(g => new MarketPresence(
g.Key, g.Count(), g.Min(x => x.Price), g.Max(x => x.Price), g.Max(x => x.LastSeenAt)))
.ToListAsync(ct);
/// <summary>Markets currently listing this skin (any wear), cheapest per market.</summary>
public Task<List<MarketPresence>> ForSkinAsync(int skinId, CancellationToken ct = default) =>
_db.MarketListings
.Where(m => m.SkinId == skinId && m.Status == Active)
.GroupBy(m => m.Marketplace)
.Select(g => new MarketPresence(
g.Key, g.Count(), g.Min(x => x.Price), g.Max(x => x.Price), g.Max(x => x.LastSeenAt)))
.ToListAsync(ct);
}