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 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 bool Headless { get; set; } = true; public BotMode Mode { get; set; } = BotMode.Trading; public MapType MapType { get; set; } = MapType.TrialOfChaos; public StashCalibration? StashCalibration { get; set; } public StashCalibration? ShopCalibration { get; set; } public bool ShowHudDebug { get; set; } public string OcrEngine { get; set; } = "WinOCR"; public KulemakSettings Kulemak { get; set; } = new(); } public class KulemakSettings { public bool Enabled { get; set; } public string InvitationTabPath { get; set; } = ""; public string LootTabPath { get; set; } = ""; public int InvitationCount { get; set; } = 15; public int RunCount { get; set; } = 15; } 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 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 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 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); // Migrate: BossRun was removed from BotMode, now it's MapType.Kulemak if (raw.Contains("\"bossRun\"") || raw.Contains("\"BossRun\"")) { const System.Text.RegularExpressions.RegexOptions ic = System.Text.RegularExpressions.RegexOptions.IgnoreCase; // Mode enum: bossRun → mapping using var doc = JsonDocument.Parse(raw); if (doc.RootElement.TryGetProperty("Mode", out var modeProp) && modeProp.GetString()?.Equals("bossRun", StringComparison.OrdinalIgnoreCase) == true) { raw = System.Text.RegularExpressions.Regex.Replace( raw, @"""Mode""\s*:\s*""bossRun""", @"""Mode"": ""mapping""", ic); raw = System.Text.RegularExpressions.Regex.Replace( raw, @"""MapType""\s*:\s*""[^""]*""", @"""MapType"": ""kulemak""", ic); Log.Information("Migrated config: Mode bossRun -> mapping + MapType kulemak"); } // MapType enum value: bossRun → kulemak raw = System.Text.RegularExpressions.Regex.Replace( raw, @"""MapType""\s*:\s*""bossRun""", @"""MapType"": ""kulemak""", ic); // Settings property name: BossRun → Kulemak raw = raw.Replace("\"BossRun\":", "\"Kulemak\":"); } var parsed = JsonSerializer.Deserialize(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/?$", ""); }