using System; using System.Globalization; using System.Text.RegularExpressions; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using CsvHelper.Configuration.Attributes; namespace Buypeeb { class YahooAuctionsItem { [JsonIgnore] public string url { get { return $"https://page.auctions.yahoo.co.jp/jp/auction/{this.id}"; } } [JsonIgnore] public string buyeeUrl { get { return $"https://buyee.jp/item/yahoo/auction/{this.id}"; } } // any items with a getter will be saved to userdata.json // anything that's configurable by the user, such as the custom name, should be saved // the id *must* be saved! // i'm also saving the original name to make it easier to tell what the items are in the userdata.json // there's not really a need for it i guess but it's my program and i can do what i want // anything with the attribute [Ignore] won't be put in the CSV, and things with [JsonIgnore] won't be put in userdata.json [Ignore] public string id { get; set; } public string name { get; set; } public int price = 0; public int winPrice; public string originalName { get; set; } public string notes { get; set; } public bool favourite { get; set; } = false; public DateTime startDate; public DateTime endDate { get; set; } public DateTime lastUpdated; public int bids; public bool autoExtension; public bool ready; public bool available; [Ignore, JsonIgnore] public bool updatedRecently { get { var later = this.lastUpdated.AddSeconds(15); return DateTime.Compare(later, this.lastUpdated) < 0; } } [JsonIgnore] public string priceJPY { get { return $"¥{this.price}"; } } [JsonIgnore] public string winPriceJPY { get { return $"¥{this.winPrice}"; } } [Ignore, JsonIgnore] public string priceAUD { get { return $"${(this.price / 75.0):f2}"; } } [Ignore, JsonIgnore] public string winPriceAUD { get { return $"${(this.winPrice / 75.0):f2}"; } } [Ignore, JsonIgnore] public bool endingToday { get { return this.endDate.DayOfYear == DateTime.UtcNow.DayOfYear; } } [Ignore, JsonIgnore] public bool hasWinPrice { get { return this.winPrice != 0; } } [Ignore, JsonIgnore] public bool endingSoon { get { return DateTime.Compare(DateTime.UtcNow.AddMinutes(10), this.endDate) > 0; } } private bool success { get; set; } // TODO: custom setter that throws an exception if set to false or something idk public YahooAuctionsItem(string id, string name) { this.id = id; this.name = name; } public YahooAuctionsItem() { // parameterless constructor for deserialisation } public void Update(string html) { // TODO: handle all the parsing errors and weird interpretation that could possibly happen here var rx = new Regex(@"var pageData ?= ?(\{.+?\});", RegexOptions.Singleline); // TODO: maybe compile and match the regex in another thread var m = rx.Match(html); if (m == null) { Console.WriteLine("no sir i don't like it"); return; } Dictionary> j_full; try { // master forgive me, but i must go all out, just this once... j_full = JsonSerializer.Deserialize>>(m.Groups[1].Value); } catch (Exception e) { Console.WriteLine("oh jeez oh man oh jeez oh man oh jeez oh man"); Console.WriteLine(m.Groups[1].Value); throw e; } var jst = TimeZoneInfo.CreateCustomTimeZone("JST", new TimeSpan(9, 0, 0), "Japan Standard Time", "Japen Standard Time"); var j = j_full["items"]; this.originalName = j["productName"]; this.startDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.ParseExact(j["starttime"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), jst); this.endDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.ParseExact(j["endtime"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), jst); this.lastUpdated = DateTime.UtcNow; this.available = j["isClosed"] == "0"; this.success = int.TryParse(j["price"], out this.price); this.success = int.TryParse(j["winPrice"], out this.winPrice); this.success = int.TryParse(j["bids"], out this.bids); if (String.IsNullOrWhiteSpace(this.name)) { this.name = this.originalName; } // as far as i can tell, neither the `pageData` nor the `conf` variables in the html seem to store whether or not the auction uses automatic extension // the `conf` variable *does* store whether or not the auction has the "early end" feature enabled, in the key `earlyed`. // unfortunately, it seems like the only way to get the auto extension info is to scrape the page for the info column that displays the auto ext status // and check whether or not it's equal to "ari" (japanese for "yes"). var autoExtensionCheck = new Regex(@"自動延長.+\n.+>(.+)<"); m = rx.Match(html); if (m.Groups[1].Value != null) { this.autoExtension = (m.Groups[1].Value == "あり"); } } public override string ToString() { return $"{this.id}: {this.name}"; } } }