138 lines
5.1 KiB
C#
138 lines
5.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
using CsvHelper.Configuration.Attributes;
|
|
// ReSharper disable MemberCanBePrivate.Global
|
|
|
|
namespace Buypeeb {
|
|
internal class YahooAuctionsItem {
|
|
public bool AutoExtension;
|
|
public bool Available;
|
|
public int Bids;
|
|
public DateTime LastUpdated;
|
|
public int Price;
|
|
public bool Ready;
|
|
public DateTime StartDate;
|
|
public bool UpdateFailed;
|
|
public int WinPrice;
|
|
|
|
public YahooAuctionsItem(string id, string name) {
|
|
this.id = id;
|
|
this.name = name;
|
|
}
|
|
|
|
public YahooAuctionsItem() {
|
|
// parameterless constructor for deserialisation
|
|
}
|
|
|
|
[JsonIgnore] public string url => $"https://page.auctions.yahoo.co.jp/jp/auction/{id}";
|
|
|
|
[JsonIgnore] public string buyeeUrl => $"https://buyee.jp/item/yahoo/auction/{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
|
|
|
|
// don't remove ID's set attribute - JSON deserialisation will silently fail to assign IDs to the auction items!
|
|
// you also don't want to private any of the setters, which will have a similar effect.
|
|
[Ignore] public string id { get; set; }
|
|
public string name { get; set; }
|
|
public string originalName { get; set; }
|
|
public string notes { get; set; }
|
|
public bool favourite { get; set; }
|
|
public DateTime endDate { get; set; }
|
|
|
|
[Ignore]
|
|
[JsonIgnore]
|
|
public bool updatedRecently {
|
|
get {
|
|
var later = LastUpdated.AddSeconds(15);
|
|
return DateTime.Compare(later, LastUpdated) < 0;
|
|
}
|
|
}
|
|
|
|
[JsonIgnore] public string priceJpy => $"¥{Price}";
|
|
|
|
[JsonIgnore] public string winPriceJpy => $"¥{WinPrice}";
|
|
|
|
[Ignore] [JsonIgnore] public string priceAud => $"${Price / 75.0:f2}";
|
|
|
|
[Ignore] [JsonIgnore] public string winPriceAud => $"${WinPrice / 75.0:f2}";
|
|
|
|
[Ignore] [JsonIgnore] public bool endingToday => endDate.DayOfYear == DateTime.UtcNow.DayOfYear;
|
|
|
|
[Ignore] [JsonIgnore] public bool hasWinPrice => WinPrice != 0;
|
|
|
|
[Ignore] [JsonIgnore] public bool endingSoon => DateTime.Compare(DateTime.UtcNow.AddMinutes(10), endDate) > 0;
|
|
|
|
private bool success { get; set; } // TODO: custom setter that throws an exception if set to false or something idk
|
|
|
|
public void AuctionEnded() {
|
|
// the page 404'd. this probably means that the auction has ended, and the page has been removed.
|
|
Available = false;
|
|
LastUpdated = DateTime.UtcNow;
|
|
UpdateFailed = true;
|
|
}
|
|
|
|
public void Update(string html) {
|
|
// TODO: handle all the parsing errors and weird interpretation that could possibly happen here
|
|
// TODO: maybe compile and match the regex in another thread
|
|
var rx = new Regex(@"var pageData ?= ?(\{.+?\});", RegexOptions.Singleline);
|
|
var m = rx.Match(html);
|
|
|
|
Dictionary<string, Dictionary<string, string>> jFull;
|
|
try {
|
|
// master forgive me, but i must go all out, just this once...
|
|
jFull = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(m.Groups[1].Value);
|
|
} catch {
|
|
Console.WriteLine("oh jeez oh man oh jeez oh man oh jeez oh man");
|
|
Console.WriteLine(m.Groups[1].Value);
|
|
throw;
|
|
}
|
|
|
|
var jst = TimeZoneInfo.CreateCustomTimeZone("JST", new TimeSpan(9, 0, 0), "Japan Standard Time",
|
|
"Japan Standard Time");
|
|
|
|
Debug.Assert(jFull != null, nameof(jFull) + " != null");
|
|
var j = jFull["items"];
|
|
originalName = j["productName"];
|
|
StartDate = TimeZoneInfo.ConvertTimeToUtc(
|
|
DateTime.ParseExact(j["starttime"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), jst);
|
|
endDate = TimeZoneInfo.ConvertTimeToUtc(
|
|
DateTime.ParseExact(j["endtime"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), jst);
|
|
LastUpdated = DateTime.UtcNow;
|
|
Available = j["isClosed"] == "0";
|
|
|
|
success = int.TryParse(j["price"], out Price);
|
|
success = int.TryParse(j["winPrice"], out WinPrice);
|
|
success = int.TryParse(j["bids"], out Bids);
|
|
|
|
if (string.IsNullOrWhiteSpace(name)) {
|
|
name = 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*, however, 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").
|
|
rx = new Regex(@"自動延長.+\n.+>(.+)<");
|
|
m = rx.Match(html);
|
|
AutoExtension = m.Groups[1].Value == "あり";
|
|
|
|
UpdateFailed = false;
|
|
}
|
|
|
|
public override string ToString() {
|
|
return $"{id}: {name}";
|
|
}
|
|
}
|
|
}
|