switched to new way

This commit is contained in:
Boki 2026-02-13 01:12:51 -05:00
parent f22d182c8f
commit 4a65c8e17b
96 changed files with 4991 additions and 10025 deletions

View file

@ -0,0 +1,29 @@
using Microsoft.Extensions.Configuration;
namespace Poe2Trade.Core;
public class AppConfig
{
public List<string> TradeUrls { get; set; } = [];
public string Poe2LogPath { get; set; } = @"C:\Program Files (x86)\Steam\steamapps\common\Path of Exile 2\logs\Client.txt";
public string Poe2WindowTitle { get; set; } = "Path of Exile 2";
public string BrowserUserDataDir { get; set; } = "./browser-data";
public int TravelTimeoutMs { get; set; } = 15000;
public int StashScanTimeoutMs { get; set; } = 10000;
public int WaitForMoreItemsMs { get; set; } = 20000;
public int BetweenTradesDelayMs { get; set; } = 5000;
public static AppConfig Load(string? configPath = null)
{
var builder = new ConfigurationBuilder();
var path = configPath ?? "appsettings.json";
if (File.Exists(path))
{
builder.AddJsonFile(path, optional: true);
}
var configuration = builder.Build();
var config = new AppConfig();
configuration.Bind(config);
return config;
}
}

View file

@ -0,0 +1,147 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Serilog;
namespace Poe2Trade.Core;
public class SavedLink
{
public string Url { get; set; } = "";
public string Name { get; set; } = "";
public bool Active { get; set; } = true;
public LinkMode Mode { get; set; } = LinkMode.Live;
public PostAction PostAction { get; set; } = PostAction.Stash;
public string AddedAt { get; set; } = DateTime.UtcNow.ToString("o");
}
public class SavedSettings
{
public bool Paused { get; set; }
public List<SavedLink> Links { get; set; } = [];
public string Poe2LogPath { get; set; } = @"C:\Program Files (x86)\Steam\steamapps\common\Path of Exile 2\logs\Client.txt";
public string Poe2WindowTitle { get; set; } = "Path of Exile 2";
public string BrowserUserDataDir { get; set; } = "./browser-data";
public int TravelTimeoutMs { get; set; } = 15000;
public int StashScanTimeoutMs { get; set; } = 10000;
public int WaitForMoreItemsMs { get; set; } = 20000;
public int BetweenTradesDelayMs { get; set; } = 5000;
public double? WindowX { get; set; }
public double? WindowY { get; set; }
public double? WindowWidth { get; set; }
public double? WindowHeight { get; set; }
}
public class ConfigStore
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};
private readonly string _filePath;
private SavedSettings _data;
public ConfigStore(string? configPath = null)
{
_filePath = configPath ?? Path.GetFullPath("config.json");
_data = Load();
}
public SavedSettings Settings => _data;
public IReadOnlyList<SavedLink> Links => _data.Links;
public void AddLink(string url, string name = "", LinkMode mode = LinkMode.Live, PostAction? postAction = null)
{
url = StripLive(url);
if (_data.Links.Any(l => l.Url == url)) return;
_data.Links.Add(new SavedLink
{
Url = url,
Name = name,
Active = true,
Mode = mode,
PostAction = postAction ?? (mode == LinkMode.Scrap ? PostAction.Salvage : PostAction.Stash),
AddedAt = DateTime.UtcNow.ToString("o")
});
Save();
}
public void RemoveLink(string url)
{
_data.Links.RemoveAll(l => l.Url == url);
Save();
}
public void RemoveLinkById(string id)
{
_data.Links.RemoveAll(l => l.Url.Split('/').Last() == id);
Save();
}
public SavedLink? UpdateLinkById(string id, Action<SavedLink> update)
{
var link = _data.Links.FirstOrDefault(l => l.Url.Split('/').Last() == id);
if (link == null) return null;
update(link);
Save();
return link;
}
public void SetPaused(bool paused)
{
_data.Paused = paused;
Save();
}
public void UpdateSettings(Action<SavedSettings> update)
{
update(_data);
Save();
}
public void Save()
{
try
{
var json = JsonSerializer.Serialize(_data, JsonOptions);
File.WriteAllText(_filePath, json);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to save config.json to {Path}", _filePath);
}
}
private SavedSettings Load()
{
if (!File.Exists(_filePath))
{
Log.Information("No config.json found at {Path}, using defaults", _filePath);
return new SavedSettings();
}
try
{
var raw = File.ReadAllText(_filePath);
var parsed = JsonSerializer.Deserialize<SavedSettings>(raw, JsonOptions);
if (parsed == null) return new SavedSettings();
// Migrate links: strip /live from URLs
foreach (var link in parsed.Links)
{
link.Url = StripLive(link.Url);
}
Log.Information("Loaded config.json from {Path} ({LinkCount} links)", _filePath, parsed.Links.Count);
return parsed;
}
catch (Exception ex)
{
Log.Warning(ex, "Failed to read config.json at {Path}, using defaults", _filePath);
return new SavedSettings();
}
}
private static string StripLive(string url) => System.Text.RegularExpressions.Regex.Replace(url, @"/live/?$", "");
}

View file

@ -0,0 +1,14 @@
namespace Poe2Trade.Core;
public static class Helpers
{
private static readonly Random Rng = new();
public static Task Sleep(int ms) => Task.Delay(ms);
public static Task RandomDelay(int minMs, int maxMs)
{
var delay = Rng.Next(minMs, maxMs + 1);
return Task.Delay(delay);
}
}

View file

@ -0,0 +1,129 @@
using Serilog;
namespace Poe2Trade.Core;
public class TradeLink
{
public string Id { get; set; } = "";
public string Url { get; set; } = "";
public string Name { get; set; } = "";
public string Label { get; set; } = "";
public bool Active { get; set; } = true;
public LinkMode Mode { get; set; } = LinkMode.Live;
public PostAction PostAction { get; set; } = PostAction.Stash;
public string AddedAt { get; set; } = DateTime.UtcNow.ToString("o");
}
public class LinkManager
{
private readonly Dictionary<string, TradeLink> _links = new();
private readonly ConfigStore _store;
public LinkManager(ConfigStore store)
{
_store = store;
}
public TradeLink AddLink(string url, string name = "", LinkMode? mode = null, PostAction? postAction = null)
{
url = StripLive(url);
var id = ExtractId(url);
var label = ExtractLabel(url);
var savedLink = _store.Links.FirstOrDefault(l => l.Url == url);
var resolvedMode = mode ?? savedLink?.Mode ?? LinkMode.Live;
var link = new TradeLink
{
Id = id,
Url = url,
Name = name != "" ? name : savedLink?.Name ?? "",
Label = label,
Active = savedLink?.Active ?? true,
Mode = resolvedMode,
PostAction = postAction ?? savedLink?.PostAction ?? (resolvedMode == LinkMode.Scrap ? PostAction.Salvage : PostAction.Stash),
AddedAt = DateTime.UtcNow.ToString("o")
};
_links[id] = link;
_store.AddLink(url, link.Name, link.Mode, link.PostAction);
Log.Information("Trade link added: {Id} {Url} mode={Mode}", id, url, link.Mode);
return link;
}
public void RemoveLink(string id)
{
if (_links.TryGetValue(id, out var link))
{
_links.Remove(id);
_store.RemoveLink(link.Url);
}
else
{
_store.RemoveLinkById(id);
}
Log.Information("Trade link removed: {Id}", id);
}
public TradeLink? ToggleLink(string id, bool active)
{
if (!_links.TryGetValue(id, out var link)) return null;
link.Active = active;
_store.UpdateLinkById(id, l => l.Active = active);
Log.Information("Trade link {Action}: {Id}", active ? "activated" : "deactivated", id);
return link;
}
public void UpdateName(string id, string name)
{
if (!_links.TryGetValue(id, out var link)) return;
link.Name = name;
_store.UpdateLinkById(id, l => l.Name = name);
}
public TradeLink? UpdateMode(string id, LinkMode mode)
{
if (!_links.TryGetValue(id, out var link)) return null;
link.Mode = mode;
_store.UpdateLinkById(id, l => l.Mode = mode);
return link;
}
public TradeLink? UpdatePostAction(string id, PostAction postAction)
{
if (!_links.TryGetValue(id, out var link)) return null;
link.PostAction = postAction;
_store.UpdateLinkById(id, l => l.PostAction = postAction);
return link;
}
public bool IsActive(string id) => _links.TryGetValue(id, out var link) && link.Active;
public List<TradeLink> GetLinks() => _links.Values.ToList();
public TradeLink? GetLink(string id) => _links.GetValueOrDefault(id);
private static string StripLive(string url) =>
System.Text.RegularExpressions.Regex.Replace(url, @"/live/?$", "");
private static string ExtractId(string url)
{
var parts = url.Split('/');
return parts.Length > 0 ? parts[^1] : url;
}
private static string ExtractLabel(string url)
{
try
{
var uri = new Uri(url);
var parts = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var poe2Idx = Array.IndexOf(parts, "poe2");
if (poe2Idx >= 0 && parts.Length > poe2Idx + 2)
{
var league = Uri.UnescapeDataString(parts[poe2Idx + 1]);
var searchId = parts[poe2Idx + 2];
return $"{league} / {searchId}";
}
}
catch { /* fallback */ }
return url.Length > 60 ? url[..60] : url;
}
}

View file

@ -0,0 +1,19 @@
using Serilog;
using Serilog.Events;
namespace Poe2Trade.Core;
public static class Logging
{
public static void Setup()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File("logs/poe2trade-.log",
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
}
}

View file

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,70 @@
namespace Poe2Trade.Core;
public record Region(int X, int Y, int Width, int Height);
public record TradeInfo(
string SearchId,
List<string> ItemIds,
string WhisperText,
long Timestamp,
string TradeUrl,
object? Page // Playwright Page reference
);
public record TradeItem(
string Id,
int W,
int H,
int StashX,
int StashY,
string Account
);
public record LogEvent(
DateTime Timestamp,
LogEventType Type,
Dictionary<string, string> Data
);
public enum LogEventType
{
AreaEntered,
WhisperReceived,
TradeAccepted,
Unknown
}
public enum TradeState
{
Idle,
Traveling,
InSellersHideout,
ScanningStash,
Buying,
WaitingForMore,
GoingHome,
InHideout,
Failed
}
public enum ScrapState
{
Idle,
Traveling,
Buying,
Salvaging,
Storing,
Failed
}
public enum LinkMode
{
Live,
Scrap
}
public enum PostAction
{
Stash,
Salvage
}