Compare commits

..

No commits in common. "1af03bf2be8ce7faa1a6447c006127d87c956663" and "ed4d6bef694e07be572b4be0981c3de3e60816d8" have entirely different histories.

21 changed files with 546 additions and 649 deletions

1
.gitignore vendored
View file

@ -9,4 +9,3 @@ yahoo.html
/buypeeb.sln /buypeeb.sln
\#*.glade# \#*.glade#
userdata.json userdata.json
*.DotSettings.user

View file

@ -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/

View file

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View file

@ -1,3 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="lynne" />
</component>

View file

@ -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>

View file

@ -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>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View file

@ -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>

View file

@ -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>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1 +0,0 @@
Lynne <lynne@bune.city> Lynnesbian <lynne@bune.city>

View file

@ -1,31 +1,32 @@
using Gtk; using Gtk;
namespace Buypeeb { 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) { private AddItemDialogue(Builder builder) : base(builder.GetObject("DialogueAdd").Handle) {
Title = "Add item"; this.Title = "Add item";
builder.Autoconnect(this); builder.Autoconnect(this);
entryURL = (Entry) builder.GetObject("EntryAddURL"); this.EntryURL = (Entry)builder.GetObject("EntryAddURL");
entryName = (Entry) builder.GetObject("EntryAddName"); this.EntryName = (Entry)builder.GetObject("EntryAddName");
DeleteEvent += Window_Shutdown; DeleteEvent += Window_Shutdown;
} }
private static void Window_Shutdown(object sender, DeleteEventArgs args) { private void Window_Shutdown(object sender, DeleteEventArgs args) {
Application.Quit(); Application.Quit();
} }
public string GetURL() { public string GetURL() {
return entryURL.Text; return this.EntryURL.Text;
} }
public string GetName() { public string GetName() {
return entryName.Text; return this.EntryName.Text;
} }
} }
} }

View file

@ -1,16 +1,15 @@
using System; using System;
using Gtk;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using GLib;
using Application = Gtk.Application;
namespace Buypeeb { namespace Buypeeb {
internal class BuypeebApp { class BuypeebApp {
[STAThread] [STAThread]
public static void Main(string[] args) { public static void Main(string[] args) {
Application.Init(); Application.Init();
var app = new Application("space.lynnesbian.Buypeeb", ApplicationFlags.None); var app = new Application("space.lynnesbian.Buypeeb", GLib.ApplicationFlags.None);
app.Register(Cancellable.Current); app.Register(GLib.Cancellable.Current);
var win = new MainWindow(); var win = new MainWindow();
app.AddWindow(win); app.AddWindow(win);
@ -19,16 +18,13 @@ namespace Buypeeb {
Application.Run(); 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) { public static string IDFromURL(string url) {
// TODO: handle invalid URLs better, or at all really // 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) url = rx.Match(url).Groups[1].Value; // remove query params (if any)
rx = new Regex(@".+\/(.+?)/?$"); 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; return id;
} }

View file

@ -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">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Buypeeb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Buypeeb { namespace Buypeeb {
internal class Settings {
class Settings {
public int updateInterval { get; set; } = 10 * 60; public int updateInterval { get; set; } = 10 * 60;
public int favouriteUpdateInterval { get; set; } = 5 * 60; public int favouriteUpdateInterval { get; set; } = 5 * 60;
public int updateIntervalCritical { get; set; } = 60; public int updateIntervalCritical { get; set; } = 60;
@ -11,32 +12,45 @@ namespace Buypeeb {
public bool autosave { get; set; } = true; public bool autosave { get; set; } = true;
public bool showFavouritesAtTopOfList { 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() { public Settings() {
// create a new watchlist from an empty dictionary if it's null, which should only happen if either this is the if (this.watchlist == null) {
// first time the program has been run, or there's something wrong with userdata.json // either this is the first time the program has been run, or there's something wrong with userdata.json
watchlist ??= new Dictionary<string, YahooAuctionsItem>(); this.watchlist = new Dictionary<string, YahooAuctionsItem>();
}
} }
public YahooAuctionsItem Watch(string url, string name) { public YahooAuctionsItem Watch(string url, string name) {
var id = BuypeebApp.IDFromURL(url); string id = BuypeebApp.IDFromURL(url);
Console.WriteLine(id); Console.WriteLine(id);
watchlist[id] = new YahooAuctionsItem(id, name); this.watchlist[id] = new YahooAuctionsItem(id, name);
return watchlist[id]; return this.watchlist[id];
} }
// TRUE if the item hasn't been updated for at least interval seconds // 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 // 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) { public bool ItemNotUpdatedSinceInterval(YahooAuctionsItem item) {
int seconds; int seconds = 1000;
if (item.favourite) { if (item.favourite) {
seconds = item.endingSoon ? favouriteUpdateIntervalCritical : favouriteUpdateInterval; if (item.endingSoon) {
} else { seconds = this.favouriteUpdateIntervalCritical;
seconds = item.endingSoon ? updateIntervalCritical : updateInterval;
} }
else {
var later = item.LastUpdated.AddSeconds(seconds); 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; return DateTime.Compare(later, DateTime.UtcNow) < 0;
} }
} }

View file

@ -2,50 +2,43 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Gtk; using Gtk;
// ReSharper disable UnusedMember.Local
// ReSharper disable UnusedParameter.Local
namespace Buypeeb { namespace Buypeeb {
internal class SettingsWindow : Window { class SettingsWindow : Window {
private readonly List<Switch> generalSwitches = new List<Switch>(); private List<Switch> generalSwitches = new List<Switch>();
private readonly List<Entry> updateIntervalEntries = new List<Entry>(); private List<Entry> updateIntervalEntries = new List<Entry>();
private readonly Settings settings; private Settings settings;
private readonly Builder builder; private Builder builder;
private readonly List<string> generalSwitchNames = new List<string> private List<String> generalSwitchNames = new List<String> { "ShowSecondsInListView", "Autosave", "ShowFavouritesAtTopOfList" };
{"ShowSecondsInListView", "Autosave", "ShowFavouritesAtTopOfList"}; private List<String> updateIntervalEntryNames = new List<String> { "UpdateInterval", "UpdateIntervalCritical", "FavouriteUpdateInterval", "FavouriteUpdateIntervalCritical" };
private readonly List<string> updateIntervalEntryNames = new List<string> public SettingsWindow(Settings settings) : this(new Builder("settings.glade"), settings) { }
{"UpdateInterval", "UpdateIntervalCritical", "FavouriteUpdateInterval", "FavouriteUpdateIntervalCritical"};
public SettingsWindow(Settings settings) : this(new Builder("settings.glade"), settings) {
}
private SettingsWindow(Builder builder, Settings settings) : base(builder.GetObject("WindowSettings").Handle) { private SettingsWindow(Builder builder, Settings settings) : base(builder.GetObject("WindowSettings").Handle) {
Title = "Buypeeb - Settings"; this.Title = "Buypeeb - Settings";
this.settings = settings; this.settings = settings;
this.builder = builder; this.builder = builder;
builder.Autoconnect(this); builder.Autoconnect(this);
foreach (var name in generalSwitchNames) { foreach (var name in this.generalSwitchNames) {
var s = (Switch)builder.GetObject($"Switch{name}"); var s = (Switch)builder.GetObject($"Switch{name}");
generalSwitches.Add(s); this.generalSwitches.Add(s);
s.Active = GetSetting<bool>(PropertyName(name)); s.Active = GetSetting<bool>(this.PropertyName(name));
} }
foreach (var name in updateIntervalEntryNames) { foreach (var name in this.updateIntervalEntryNames) {
var e = (Entry)builder.GetObject($"Entry{name}"); var e = (Entry)builder.GetObject($"Entry{name}");
updateIntervalEntries.Add(e); this.updateIntervalEntries.Add(e);
e.Text = GetSetting<int>(PropertyName(name)).ToString(); e.Text = GetSetting<int>(this.PropertyName(name)).ToString();
} }
} }
private T GetSetting<T>(string property) { 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) { 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) { private string PropertyName(string property) {
@ -55,43 +48,40 @@ namespace Buypeeb {
private void ButtonSaveClicked(object sender, EventArgs args) { private void ButtonSaveClicked(object sender, EventArgs args) {
// first, validate all the intervals // first, validate all the intervals
var failed = false; bool failed = false;
foreach (var name in updateIntervalEntryNames) { foreach (var name in this.updateIntervalEntryNames) {
var e = (Entry)builder.GetObject($"Entry{name}"); var e = (Entry)builder.GetObject($"Entry{name}");
if (!int.TryParse(e.Text, out var result)) { if (!int.TryParse(e.Text, out int result)) {
failed = true; failed = true;
} else { }
else {
if (result < 30 || result > 6000) { if (result < 30 || result > 6000) {
failed = true; failed = true;
} }
} }
if (!failed) { 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.");
}
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.Run();
md.Dispose(); md.Dispose();
return; return;
} }
}
// validation success! // validation success!
foreach (var name in updateIntervalEntryNames) { foreach (var name in this.updateIntervalEntryNames) {
SetSetting(PropertyName(name), int.Parse(((Entry) builder.GetObject($"Entry{name}")).Text)); this.SetSetting<int>(PropertyName(name), int.Parse((builder.GetObject($"Entry{name}") as Entry).Text));
} }
foreach (var name in generalSwitchNames) { foreach (var name in this.generalSwitchNames) {
SetSetting(PropertyName(name), ((Switch) builder.GetObject($"Switch{name}")).Active); this.SetSetting<bool>(PropertyName(name), (builder.GetObject($"Switch{name}") as Switch).Active);
} }
this.Dispose();
Dispose();
} }
private void ButtonCancelClicked(object sender, EventArgs args) { private void ButtonCancelClicked(object sender, EventArgs args) {
Dispose(); this.Dispose();
} }
} }
} }

View file

@ -1,64 +1,71 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using CsvHelper.Configuration.Attributes; using CsvHelper.Configuration.Attributes;
namespace Buypeeb { namespace Buypeeb {
internal class YahooAuctionsItem { class YahooAuctionsItem {
[JsonIgnore] public string url => $"https://page.auctions.yahoo.co.jp/jp/auction/{id}"; [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 // 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 // anything that's configurable by the user, such as the custom name, should be saved
// the id *must* 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 // 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 // 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 // anything with the attribute [Ignore] won't be put in the CSV, and things with [JsonIgnore] won't be put in userdata.json
// userdata.json [Ignore]
// ReSharper disable once MemberCanBePrivate.Global public string id { get; set; }
// 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; }
public string name { get; set; } public string name { get; set; }
public int Price; public int price = 0;
public int WinPrice; public int winPrice;
public string originalName { get; set; } public string originalName { get; set; }
public string notes { get; set; } public string notes { get; set; }
public bool favourite { get; set; } public bool favourite { get; set; } = false;
public DateTime StartDate; public DateTime startDate;
public DateTime endDate { get; set; } public DateTime endDate { get; set; }
public DateTime LastUpdated; public DateTime lastUpdated;
public int Bids; public int bids;
public bool AutoExtension; public bool autoExtension;
public bool Ready; public bool ready;
public bool Available; public bool available;
public bool UpdateFailed;
[Ignore, JsonIgnore] [Ignore, JsonIgnore]
public bool updatedRecently { public bool updatedRecently {
get { get {
var later = LastUpdated.AddSeconds(15); var later = this.lastUpdated.AddSeconds(15);
return DateTime.Compare(later, LastUpdated) < 0; return DateTime.Compare(later, this.lastUpdated) < 0;
} }
} }
[JsonIgnore] public string priceJpy => $"¥{Price}"; [JsonIgnore]
public string priceJPY { get { return $"¥{this.price}"; } }
[JsonIgnore] public string winPriceJpy => $"¥{WinPrice}"; [JsonIgnore]
public string winPriceJPY { get { return $"¥{this.winPrice}"; } }
[Ignore, JsonIgnore] public string priceAud => $"${(Price / 75.0):f2}"; [Ignore, JsonIgnore]
public string priceAUD { get { return $"${(this.price / 75.0):f2}"; } }
[Ignore, JsonIgnore] public string winPriceAud => $"${(WinPrice / 75.0):f2}"; [Ignore, JsonIgnore]
public string winPriceAUD { get { return $"${(this.winPrice / 75.0):f2}"; } }
[Ignore, JsonIgnore] public bool endingToday => endDate.DayOfYear == DateTime.UtcNow.DayOfYear; [Ignore, JsonIgnore]
public bool endingToday { get { return this.endDate.DayOfYear == DateTime.UtcNow.DayOfYear; } }
[Ignore, JsonIgnore] public bool hasWinPrice => WinPrice != 0; [Ignore, JsonIgnore]
public bool hasWinPrice { get { return this.winPrice != 0; } }
[Ignore, JsonIgnore] public bool endingSoon => DateTime.Compare(DateTime.UtcNow.AddMinutes(10), endDate) > 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 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) { public YahooAuctionsItem(string id, string name) {
@ -70,63 +77,56 @@ namespace Buypeeb {
// parameterless constructor for deserialisation // 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) { public void Update(string html) {
// TODO: handle all the parsing errors and weird interpretation that could possibly happen here // TODO: handle all the parsing errors and weird interpretation that could possibly happen here
var rx = new Regex(@"var pageData ?= ?(\{.+?\});", var rx = new Regex(@"var pageData ?= ?(\{.+?\});", RegexOptions.Singleline); // TODO: maybe compile and match the regex in another thread
RegexOptions.Singleline); // TODO: maybe compile and match the regex in another thread
var m = rx.Match(html); 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 { try {
// master forgive me, but i must go all out, just this once... // master forgive me, but i must go all out, just this once...
jFull = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(m.Groups[1].Value); j_full = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(m.Groups[1].Value);
} catch { }
catch (Exception e) {
Console.WriteLine("oh jeez oh man oh jeez oh man oh jeez oh man"); Console.WriteLine("oh jeez oh man oh jeez oh man oh jeez oh man");
Console.WriteLine(m.Groups[1].Value); Console.WriteLine(m.Groups[1].Value);
throw; throw e;
} }
var jst = TimeZoneInfo.CreateCustomTimeZone("JST", new TimeSpan(9, 0, 0), "Japan Standard Time", var jst = TimeZoneInfo.CreateCustomTimeZone("JST", new TimeSpan(9, 0, 0), "Japan Standard Time", "Japen Standard Time");
"Japan Standard Time");
var j = jFull["items"]; var j = j_full["items"];
originalName = j["productName"]; this.originalName = j["productName"];
StartDate = TimeZoneInfo.ConvertTimeToUtc( this.startDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.ParseExact(j["starttime"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), jst);
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);
endDate = TimeZoneInfo.ConvertTimeToUtc( this.lastUpdated = DateTime.UtcNow;
DateTime.ParseExact(j["endtime"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), jst); this.available = j["isClosed"] == "0";
LastUpdated = DateTime.UtcNow;
Available = j["isClosed"] == "0";
success = int.TryParse(j["price"], out Price); this.success = int.TryParse(j["price"], out this.price);
success = int.TryParse(j["winPrice"], out WinPrice); this.success = int.TryParse(j["winPrice"], out this.winPrice);
success = int.TryParse(j["bids"], out Bids); this.success = int.TryParse(j["bids"], out this.bids);
if (string.IsNullOrWhiteSpace(name)) { if (String.IsNullOrWhiteSpace(this.name)) {
name = originalName; 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 // 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 auction uses automatic extension, the `conf` variable *does*, however, store whether or not the auction // the `conf` variable *does* store whether or not the auction has the "early end" feature enabled, in the key `earlyed`.
// has the "early end" feature enabled, in the key `earlyed`. unfortunately, it seems like the only way to get the // 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
// auto extension info is to scrape the page for the info column that displays the auto ext status and check // and check whether or not it's equal to "ari" (japanese for "yes").
// whether or not it's equal to "ari" (japanese for "yes"). var autoExtensionCheck = new Regex(@"自動延長.+\n.+>(.+)<");
rx = new Regex(@"自動延長.+\n.+>(.+)<");
m = rx.Match(html); m = rx.Match(html);
AutoExtension = (m.Groups[1].Value == "あり"); if (m.Groups[1].Value != null) {
this.autoExtension = (m.Groups[1].Value == "あり");
UpdateFailed = false; }
} }
public override string ToString() { public override string ToString() {
return $"{id}: {name}"; return $"{this.id}: {this.name}";
} }
} }
} }

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp5.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<StartupObject>Buypeeb.BuypeebApp</StartupObject> <StartupObject>Buypeeb.BuypeebApp</StartupObject>
</PropertyGroup> </PropertyGroup>
@ -14,8 +14,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CsvHelper" Version="27.1.0" /> <PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="GtkSharp" Version="3.24.24.34" /> <PackageReference Include="GtkSharp" Version="3.22.25.128" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,11 +1,9 @@
buypeeb buypeeb
=== ===
a simple dotnet core program that allows you to track auctions on yahoo auctions japan, written in c#. designed for and 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.
with linux, but works on windows.
## quick start (arch linux) ## quick start (arch linux)
```bash ```bash
git clone https://git.bune.city/lynnesbian/buypeeb-cs git clone https://git.bune.city/lynnesbian/buypeeb-cs
cd buypeeb-cs cd buypeeb-cs
@ -14,7 +12,6 @@ dotnet run
``` ```
## quick install (arch linux) ## quick install (arch linux)
```bash ```bash
mkdir buypeeb mkdir buypeeb
cd 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 makepkg -si # installs to /usr/bin/buypeeb
buypeeb buypeeb
``` ```
--- ---
## installing prerequisites ## installing prerequisites
you'll need the [dotnet core sdk](https://dotnet.microsoft.com/)
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.
### debian ### 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 ### arch linux
``` ```
sudo pacman -S dotnet-sdk 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 ### windows
- download and install ".NET Core SDK" from [this page](https://dotnet.microsoft.com/download)
- 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)
- 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 i have no idea how to install mono with gtksharp properly on windows, and believe me i tried
--- ---
## compiling ## compiling
``` ```
dotnet build dotnet build
``` ```
--- ---
## running ## running
``` ```
dotnet run dotnet run
``` ```
--- ---
## compiling a standalone version ## compiling a standalone version
the standalone versions are rather big, so it's a good idea to compress them for distribution. the standalone versions are rather big, so it's a good idea to compress them for distribution.
### linux ### linux
run `./release.sh`. run `./release.sh`.
the binary will be located at `./out/release/linux/buypeeb`, with a [zstandard](https://facebook.github.io/zstd/) 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.
compressed version at `buypeeb.tar.zst` in the same directory.
### windows ### windows
run `.\release.ps1`. run `.\release.ps1`.
the binary will be located at `.\out\release\windows\buybeep.exe`, with a zip compressed version at `buypeep.zip` in the the binary will be located at `.\out\release\windows\buybeep.exe`, with a zip compressed version at `buypeep.zip` in the same directory.
same directory.
unfortunately, however, this program won't work on machines that don't have GTK3 installed, and installing GTK3 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.
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.

View file

@ -429,7 +429,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkToolButton" id="HelpButton"> <object class="GtkToolButton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Help</property> <property name="tooltip_text" translatable="yes">Help</property>