Compare commits
No commits in common. "1af03bf2be8ce7faa1a6447c006127d87c956663" and "ed4d6bef694e07be572b4be0981c3de3e60816d8" have entirely different histories.
1af03bf2be
...
ed4d6bef69
21 changed files with 546 additions and 649 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,4 +9,3 @@ yahoo.html
|
|||
/buypeeb.sln
|
||||
\#*.glade#
|
||||
userdata.json
|
||||
*.DotSettings.user
|
||||
|
|
13
.idea/.idea.buypeeb.dir/.idea/.gitignore
vendored
13
.idea/.idea.buypeeb.dir/.idea/.gitignore
vendored
|
@ -1,13 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/projectSettingsUpdater.xml
|
||||
/.idea.buypeeb.iml
|
||||
/contentModel.xml
|
||||
/modules.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
|
@ -1,5 +0,0 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
|
@ -1,3 +0,0 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="lynne" />
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
|
@ -1,9 +0,0 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyInconsistentIndentationInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RegExpRepeatedSpace" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1
.mailmap
1
.mailmap
|
@ -1 +0,0 @@
|
|||
Lynne <lynne@bune.city> Lynnesbian <lynne@bune.city>
|
|
@ -1,31 +1,32 @@
|
|||
using Gtk;
|
||||
|
||||
namespace Buypeeb {
|
||||
internal class AddItemDialogue : Dialog {
|
||||
private readonly Entry entryURL;
|
||||
private readonly Entry entryName;
|
||||
|
||||
public AddItemDialogue() : this(new Builder("add.glade")) {
|
||||
}
|
||||
class AddItemDialogue : Dialog {
|
||||
private Entry EntryURL;
|
||||
private Entry EntryName;
|
||||
|
||||
public AddItemDialogue() : this(new Builder("add.glade")) { }
|
||||
|
||||
private AddItemDialogue(Builder builder) : base(builder.GetObject("DialogueAdd").Handle) {
|
||||
Title = "Add item";
|
||||
this.Title = "Add item";
|
||||
builder.Autoconnect(this);
|
||||
entryURL = (Entry) builder.GetObject("EntryAddURL");
|
||||
entryName = (Entry) builder.GetObject("EntryAddName");
|
||||
this.EntryURL = (Entry)builder.GetObject("EntryAddURL");
|
||||
this.EntryName = (Entry)builder.GetObject("EntryAddName");
|
||||
DeleteEvent += Window_Shutdown;
|
||||
}
|
||||
|
||||
private static void Window_Shutdown(object sender, DeleteEventArgs args) {
|
||||
private void Window_Shutdown(object sender, DeleteEventArgs args) {
|
||||
Application.Quit();
|
||||
}
|
||||
|
||||
public string GetURL() {
|
||||
return entryURL.Text;
|
||||
return this.EntryURL.Text;
|
||||
}
|
||||
|
||||
public string GetName() {
|
||||
return entryName.Text;
|
||||
return this.EntryName.Text;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
using System;
|
||||
using Gtk;
|
||||
using System.Text.RegularExpressions;
|
||||
using GLib;
|
||||
using Application = Gtk.Application;
|
||||
|
||||
namespace Buypeeb {
|
||||
internal class BuypeebApp {
|
||||
class BuypeebApp {
|
||||
[STAThread]
|
||||
public static void Main(string[] args) {
|
||||
Application.Init();
|
||||
|
||||
var app = new Application("space.lynnesbian.Buypeeb", ApplicationFlags.None);
|
||||
app.Register(Cancellable.Current);
|
||||
var app = new Application("space.lynnesbian.Buypeeb", GLib.ApplicationFlags.None);
|
||||
app.Register(GLib.Cancellable.Current);
|
||||
|
||||
var win = new MainWindow();
|
||||
app.AddWindow(win);
|
||||
|
@ -19,16 +18,13 @@ namespace Buypeeb {
|
|||
Application.Run();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// i prefer "IDFromURL" to "IdFromUrl". also i will never forgive microsoft for the name of XmlHTTPRequest and you
|
||||
// may consider this an act of spite/revenge
|
||||
public static string IDFromURL(string url) {
|
||||
// TODO: handle invalid URLs better, or at all really
|
||||
var rx = new Regex(@"^([^?#]+)");
|
||||
Regex rx = new Regex(@"^([^?#]+)");
|
||||
url = rx.Match(url).Groups[1].Value; // remove query params (if any)
|
||||
|
||||
rx = new Regex(@".+\/(.+?)/?$");
|
||||
var id = rx.Match(url).Groups[1].Value; // extracts "k12345" from "https://buypeeb.biz/whatever/k12345/"
|
||||
string id = rx.Match(url).Groups[1].Value; // extracts "k12345" from "https://buypeeb.biz/whatever/k12345/"
|
||||
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=autosave/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Buypeeb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
735
MainWindow.cs
735
MainWindow.cs
File diff suppressed because it is too large
Load diff
42
Settings.cs
42
Settings.cs
|
@ -2,7 +2,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Buypeeb {
|
||||
internal class Settings {
|
||||
|
||||
class Settings {
|
||||
public int updateInterval { get; set; } = 10 * 60;
|
||||
public int favouriteUpdateInterval { get; set; } = 5 * 60;
|
||||
public int updateIntervalCritical { get; set; } = 60;
|
||||
|
@ -11,32 +12,45 @@ namespace Buypeeb {
|
|||
public bool autosave { get; set; } = true;
|
||||
public bool showFavouritesAtTopOfList { get; set; } = true;
|
||||
|
||||
public Dictionary<string, YahooAuctionsItem> watchlist { get; set; }
|
||||
public Dictionary<string, YahooAuctionsItem> watchlist {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Settings() {
|
||||
// create a new watchlist from an empty dictionary if it's null, which should only happen if either this is the
|
||||
// first time the program has been run, or there's something wrong with userdata.json
|
||||
watchlist ??= new Dictionary<string, YahooAuctionsItem>();
|
||||
if (this.watchlist == null) {
|
||||
// either this is the first time the program has been run, or there's something wrong with userdata.json
|
||||
this.watchlist = new Dictionary<string, YahooAuctionsItem>();
|
||||
}
|
||||
}
|
||||
|
||||
public YahooAuctionsItem Watch(string url, string name) {
|
||||
var id = BuypeebApp.IDFromURL(url);
|
||||
string id = BuypeebApp.IDFromURL(url);
|
||||
Console.WriteLine(id);
|
||||
watchlist[id] = new YahooAuctionsItem(id, name);
|
||||
return watchlist[id];
|
||||
this.watchlist[id] = new YahooAuctionsItem(id, name);
|
||||
return this.watchlist[id];
|
||||
}
|
||||
|
||||
// TRUE if the item hasn't been updated for at least interval seconds
|
||||
// for example, if the interval is 10, and the item hasn't been updated since 20 seconds ago, this will be TRUE
|
||||
public bool ItemNotUpdatedSinceInterval(YahooAuctionsItem item) {
|
||||
int seconds;
|
||||
int seconds = 1000;
|
||||
if (item.favourite) {
|
||||
seconds = item.endingSoon ? favouriteUpdateIntervalCritical : favouriteUpdateInterval;
|
||||
} else {
|
||||
seconds = item.endingSoon ? updateIntervalCritical : updateInterval;
|
||||
if (item.endingSoon) {
|
||||
seconds = this.favouriteUpdateIntervalCritical;
|
||||
}
|
||||
|
||||
var later = item.LastUpdated.AddSeconds(seconds);
|
||||
else {
|
||||
seconds = this.favouriteUpdateInterval;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (item.endingSoon) {
|
||||
seconds = this.updateIntervalCritical;
|
||||
}
|
||||
else {
|
||||
seconds = this.updateInterval;
|
||||
}
|
||||
}
|
||||
var later = item.lastUpdated.AddSeconds(seconds);
|
||||
return DateTime.Compare(later, DateTime.UtcNow) < 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,50 +2,43 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Gtk;
|
||||
|
||||
// ReSharper disable UnusedMember.Local
|
||||
// ReSharper disable UnusedParameter.Local
|
||||
|
||||
namespace Buypeeb {
|
||||
internal class SettingsWindow : Window {
|
||||
private readonly List<Switch> generalSwitches = new List<Switch>();
|
||||
private readonly List<Entry> updateIntervalEntries = new List<Entry>();
|
||||
private readonly Settings settings;
|
||||
private readonly Builder builder;
|
||||
class SettingsWindow : Window {
|
||||
private List<Switch> generalSwitches = new List<Switch>();
|
||||
private List<Entry> updateIntervalEntries = new List<Entry>();
|
||||
private Settings settings;
|
||||
private Builder builder;
|
||||
|
||||
private readonly List<string> generalSwitchNames = new List<string>
|
||||
{"ShowSecondsInListView", "Autosave", "ShowFavouritesAtTopOfList"};
|
||||
private List<String> generalSwitchNames = new List<String> { "ShowSecondsInListView", "Autosave", "ShowFavouritesAtTopOfList" };
|
||||
private List<String> updateIntervalEntryNames = new List<String> { "UpdateInterval", "UpdateIntervalCritical", "FavouriteUpdateInterval", "FavouriteUpdateIntervalCritical" };
|
||||
|
||||
private readonly List<string> updateIntervalEntryNames = new List<string>
|
||||
{"UpdateInterval", "UpdateIntervalCritical", "FavouriteUpdateInterval", "FavouriteUpdateIntervalCritical"};
|
||||
|
||||
public SettingsWindow(Settings settings) : this(new Builder("settings.glade"), settings) {
|
||||
}
|
||||
public SettingsWindow(Settings settings) : this(new Builder("settings.glade"), settings) { }
|
||||
|
||||
private SettingsWindow(Builder builder, Settings settings) : base(builder.GetObject("WindowSettings").Handle) {
|
||||
Title = "Buypeeb - Settings";
|
||||
this.Title = "Buypeeb - Settings";
|
||||
this.settings = settings;
|
||||
this.builder = builder;
|
||||
builder.Autoconnect(this);
|
||||
|
||||
foreach (var name in generalSwitchNames) {
|
||||
var s = (Switch) builder.GetObject($"Switch{name}");
|
||||
generalSwitches.Add(s);
|
||||
s.Active = GetSetting<bool>(PropertyName(name));
|
||||
foreach (var name in this.generalSwitchNames) {
|
||||
var s = (Switch)builder.GetObject($"Switch{name}");
|
||||
this.generalSwitches.Add(s);
|
||||
s.Active = GetSetting<bool>(this.PropertyName(name));
|
||||
}
|
||||
|
||||
foreach (var name in updateIntervalEntryNames) {
|
||||
var e = (Entry) builder.GetObject($"Entry{name}");
|
||||
updateIntervalEntries.Add(e);
|
||||
e.Text = GetSetting<int>(PropertyName(name)).ToString();
|
||||
foreach (var name in this.updateIntervalEntryNames) {
|
||||
var e = (Entry)builder.GetObject($"Entry{name}");
|
||||
this.updateIntervalEntries.Add(e);
|
||||
e.Text = GetSetting<int>(this.PropertyName(name)).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private T GetSetting<T>(string property) {
|
||||
return (T) settings.GetType().GetProperty(property).GetValue(settings, null);
|
||||
return (T)this.settings.GetType().GetProperty(property).GetValue(this.settings, null);
|
||||
}
|
||||
|
||||
private void SetSetting<T>(string property, T value) {
|
||||
settings.GetType().GetProperty(property).SetValue(settings, value);
|
||||
this.settings.GetType().GetProperty(property).SetValue(this.settings, value);
|
||||
}
|
||||
|
||||
private string PropertyName(string property) {
|
||||
|
@ -55,43 +48,40 @@ namespace Buypeeb {
|
|||
|
||||
private void ButtonSaveClicked(object sender, EventArgs args) {
|
||||
// first, validate all the intervals
|
||||
var failed = false;
|
||||
foreach (var name in updateIntervalEntryNames) {
|
||||
var e = (Entry) builder.GetObject($"Entry{name}");
|
||||
if (!int.TryParse(e.Text, out var result)) {
|
||||
bool failed = false;
|
||||
foreach (var name in this.updateIntervalEntryNames) {
|
||||
var e = (Entry)builder.GetObject($"Entry{name}");
|
||||
if (!int.TryParse(e.Text, out int result)) {
|
||||
failed = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (result < 30 || result > 6000) {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok,
|
||||
"Update intervals must be a whole number between 30 and 6000.");
|
||||
if (failed) {
|
||||
var md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "Update intervals must be a whole number between 30 and 6000.");
|
||||
md.Run();
|
||||
md.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// validation success!
|
||||
|
||||
foreach (var name in updateIntervalEntryNames) {
|
||||
SetSetting(PropertyName(name), int.Parse(((Entry) builder.GetObject($"Entry{name}")).Text));
|
||||
foreach (var name in this.updateIntervalEntryNames) {
|
||||
this.SetSetting<int>(PropertyName(name), int.Parse((builder.GetObject($"Entry{name}") as Entry).Text));
|
||||
}
|
||||
|
||||
foreach (var name in generalSwitchNames) {
|
||||
SetSetting(PropertyName(name), ((Switch) builder.GetObject($"Switch{name}")).Active);
|
||||
foreach (var name in this.generalSwitchNames) {
|
||||
this.SetSetting<bool>(PropertyName(name), (builder.GetObject($"Switch{name}") as Switch).Active);
|
||||
}
|
||||
|
||||
Dispose();
|
||||
this.Dispose();
|
||||
}
|
||||
|
||||
private void ButtonCancelClicked(object sender, EventArgs args) {
|
||||
Dispose();
|
||||
this.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +1,71 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using CsvHelper.Configuration.Attributes;
|
||||
|
||||
namespace Buypeeb {
|
||||
internal class YahooAuctionsItem {
|
||||
[JsonIgnore] public string url => $"https://page.auctions.yahoo.co.jp/jp/auction/{id}";
|
||||
class YahooAuctionsItem {
|
||||
[JsonIgnore]
|
||||
public string url {
|
||||
get {
|
||||
return $"https://page.auctions.yahoo.co.jp/jp/auction/{this.id}";
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore] public string buyeeUrl => $"https://buyee.jp/item/yahoo/auction/{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
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// don't remove ID's set attribute - JSON deserialisation will silently fail to assign IDs to the auction items!
|
||||
[Ignore] public string id { get; set; }
|
||||
// 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;
|
||||
public int WinPrice;
|
||||
public int price = 0;
|
||||
public int winPrice;
|
||||
public string originalName { get; set; }
|
||||
public string notes { get; set; }
|
||||
public bool favourite { get; set; }
|
||||
public DateTime StartDate;
|
||||
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;
|
||||
public bool UpdateFailed;
|
||||
public DateTime lastUpdated;
|
||||
public int bids;
|
||||
public bool autoExtension;
|
||||
public bool ready;
|
||||
public bool available;
|
||||
|
||||
[Ignore, JsonIgnore]
|
||||
public bool updatedRecently {
|
||||
get {
|
||||
var later = LastUpdated.AddSeconds(15);
|
||||
return DateTime.Compare(later, LastUpdated) < 0;
|
||||
var later = this.lastUpdated.AddSeconds(15);
|
||||
return DateTime.Compare(later, this.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;
|
||||
|
||||
[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) {
|
||||
|
@ -70,63 +77,56 @@ namespace Buypeeb {
|
|||
// parameterless constructor for deserialisation
|
||||
}
|
||||
|
||||
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
|
||||
var rx = new Regex(@"var pageData ?= ?(\{.+?\});",
|
||||
RegexOptions.Singleline); // TODO: maybe compile and match the regex in another thread
|
||||
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<string, Dictionary<string, string>> jFull;
|
||||
Dictionary<string, Dictionary<string, string>> j_full;
|
||||
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 {
|
||||
j_full = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(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;
|
||||
throw e;
|
||||
}
|
||||
|
||||
var jst = TimeZoneInfo.CreateCustomTimeZone("JST", new TimeSpan(9, 0, 0), "Japan Standard Time",
|
||||
"Japan Standard Time");
|
||||
var jst = TimeZoneInfo.CreateCustomTimeZone("JST", new TimeSpan(9, 0, 0), "Japan Standard Time", "Japen Standard Time");
|
||||
|
||||
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";
|
||||
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";
|
||||
|
||||
success = int.TryParse(j["price"], out Price);
|
||||
success = int.TryParse(j["winPrice"], out WinPrice);
|
||||
success = int.TryParse(j["bids"], out Bids);
|
||||
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(name)) {
|
||||
name = originalName;
|
||||
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*, 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.+>(.+)<");
|
||||
// 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);
|
||||
AutoExtension = (m.Groups[1].Value == "あり");
|
||||
|
||||
UpdateFailed = false;
|
||||
if (m.Groups[1].Value != null) {
|
||||
this.autoExtension = (m.Groups[1].Value == "あり");
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"{id}: {name}";
|
||||
return $"{this.id}: {this.name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp5.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<StartupObject>Buypeeb.BuypeebApp</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -14,8 +14,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="27.1.0" />
|
||||
<PackageReference Include="GtkSharp" Version="3.24.24.34" />
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
45
readme.md
45
readme.md
|
@ -1,11 +1,9 @@
|
|||
buypeeb
|
||||
===
|
||||
|
||||
a simple dotnet core program that allows you to track auctions on yahoo auctions japan, written in c#. designed for and
|
||||
with linux, but works on windows.
|
||||
a simple dotnet core program that allows you to track auctions on yahoo auctions japan, written in c#. designed for and with linux, but works on windows.
|
||||
|
||||
## quick start (arch linux)
|
||||
|
||||
```bash
|
||||
git clone https://git.bune.city/lynnesbian/buypeeb-cs
|
||||
cd buypeeb-cs
|
||||
|
@ -14,7 +12,6 @@ dotnet run
|
|||
```
|
||||
|
||||
## quick install (arch linux)
|
||||
|
||||
```bash
|
||||
mkdir buypeeb
|
||||
cd buypeeb
|
||||
|
@ -22,75 +19,49 @@ curl -O https://git.bune.city/lynnesbian/buypeeb-cs/raw/branch/master/PKGBUILD
|
|||
makepkg -si # installs to /usr/bin/buypeeb
|
||||
buypeeb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## installing prerequisites
|
||||
|
||||
you'll need the [dotnet core sdk](https://dotnet.microsoft.com/). buypeeb is written for .NET 5.0, so you'll need to
|
||||
make sure you install that SDK version.
|
||||
you'll need the [dotnet core sdk](https://dotnet.microsoft.com/)
|
||||
|
||||
### debian
|
||||
|
||||
follow [these instructions](https://docs.microsoft.com/en-us/dotnet/core/install/linux-debian) to add the dotnet repo
|
||||
and install dotnet-sdk.
|
||||
follow [these instructions](https://docs.microsoft.com/en-us/dotnet/core/install/linux-debian) to add the dotnet repo and install dotnet-sdk.
|
||||
|
||||
### arch linux
|
||||
|
||||
```
|
||||
sudo pacman -S dotnet-sdk
|
||||
```
|
||||
|
||||
### other linux distros
|
||||
|
||||
follow [these instructions](https://docs.microsoft.com/en-us/dotnet/core/install/linux) to install the relevant dotnet
|
||||
SDK.
|
||||
|
||||
### windows
|
||||
|
||||
- download and install ".NET 5.0" from [this page](https://dotnet.microsoft.com/download/dotnet)
|
||||
- run `dotnet build` - this *should* pull in gtk3 automatically - if not, you'll need
|
||||
to [install it yourself](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases/latest)
|
||||
- download and install ".NET Core SDK" from [this page](https://dotnet.microsoft.com/download)
|
||||
- run `dotnet build` - this *should* pull in gtk3 automatically - if not, you'll need to [install it yourself](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases/latest)
|
||||
|
||||
i have no idea how to install mono with gtksharp properly on windows, and believe me i tried
|
||||
|
||||
---
|
||||
|
||||
## compiling
|
||||
|
||||
```
|
||||
dotnet build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## running
|
||||
|
||||
```
|
||||
dotnet run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## compiling a standalone version
|
||||
|
||||
the standalone versions are rather big, so it's a good idea to compress them for distribution.
|
||||
|
||||
### linux
|
||||
|
||||
run `./release.sh`.
|
||||
|
||||
the binary will be located at `./out/release/linux/buypeeb`, with a [zstandard](https://facebook.github.io/zstd/)
|
||||
compressed version at `buypeeb.tar.zst` in the same directory.
|
||||
the binary will be located at `./out/release/linux/buypeeb`, with a [zstandard](https://facebook.github.io/zstd/) compressed version at `buypeeb.tar.zst` in the same directory.
|
||||
|
||||
### windows
|
||||
|
||||
run `.\release.ps1`.
|
||||
|
||||
the binary will be located at `.\out\release\windows\buybeep.exe`, with a zip compressed version at `buypeep.zip` in the
|
||||
same directory.
|
||||
the binary will be located at `.\out\release\windows\buybeep.exe`, with a zip compressed version at `buypeep.zip` in the same directory.
|
||||
|
||||
unfortunately, however, this program won't work on machines that don't have GTK3 installed, and installing GTK3
|
||||
is [quite an undertaking](https://www.gtk.org/docs/installations/windows/). a solution for this
|
||||
is [in the works](https://github.com/GtkSharp/GtkSharp/issues/119), but there's not really anything to be done about it
|
||||
right now.
|
||||
unfortunately, however, this program won't work on machines that don't have GTK3 installed, and installing GTK3 is [quite an undertaking](https://www.gtk.org/docs/installations/windows/). a solution for this is [in the works](https://github.com/GtkSharp/GtkSharp/issues/119), but there's not really anything to be done about it right now.
|
||||
|
|
|
@ -429,7 +429,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="HelpButton">
|
||||
<object class="GtkToolButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Help</property>
|
||||
|
|
Loading…
Reference in a new issue