diff --git a/PriceChecker.Core/Services/TrickyHttpClient.cs b/PriceChecker.Core/Services/TrickyHttpClient.cs index 77141cc..18de2f2 100644 --- a/PriceChecker.Core/Services/TrickyHttpClient.cs +++ b/PriceChecker.Core/Services/TrickyHttpClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -16,7 +17,7 @@ public class TrickyHttpClient : ITrickyHttpClient { private readonly ConcurrentDictionary _lockers = new(); private readonly ILogger _logger; - private static object _locker = new object(); + private static object _locker = new(); private const int DELAY_MS = 500; @@ -44,12 +45,18 @@ public async Task DownloadContent(string url) private async Task DownloadInternal(string url) { - var httpClient = new HttpClient(); + var handler = new HttpClientHandler() + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + var httpClient = new HttpClient(handler); // To confuse the hosts httpClient.DefaultRequestHeaders.Add("X-Cookies-Accepted", "1"); - httpClient.DefaultRequestHeaders.Add("accept", "text/html"); - httpClient.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"); + httpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9"); + httpClient.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.9"); + httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate"); + httpClient.DefaultRequestHeaders.Add("User-Agent", CreateRandomUserAgent()); var response = await httpClient.GetAsync(url); if (!response.IsSuccessStatusCode) @@ -60,5 +67,53 @@ private async Task DownloadInternal(string url) } return await response.Content.ReadAsStringAsync(); } + + private string CreateRandomUserAgent() + { + var platform = new [] { "Machintosh", "Windows", "X11" }.TakeRandom(); + var os = (platform switch { + "Machintosh" => new [] { "68K", "PPC" }, + "Windows" => new [] { "Win3.11", "WinNT3.51", "WinNT4.0", "Windows NT 5.0", "Windows NT 5.1", "Windows NT 5.2", "Windows NT 6.0", "Windows NT 6.1", "Windows NT 6.2", "Win95", "Win98", "Win 9x 4.90", "WindowsCE" }, + "X11" => new [] { "Linux i686", "Linux x86_64" }, + _ => new string[] {} + }).TakeRandom(); + var browser = new [] { "Chrome", "Firefox", "IE" }.TakeRandom(); + + if (browser == "Chrome") + { + var webkit = Utils.RandomInt(500, 599).ToString(); + var version = $"{Utils.RandomInt(0, 24)}.0{Utils.RandomInt(0, 1500)}.{Utils.RandomInt(0, 999)}"; + + return $"Mozilla/5.0 ({os}) AppleWebKit{webkit}.0 (KHTML, live Gecko) Chrome/{version} Safari/{webkit}"; + } + if (browser == "Firefox") + { + var year = Utils.RandomInt(2000, 2021); + var month = Utils.RandomInt(1, 12); + var day = Utils.RandomInt(1, 28); + var gecko = $"{year}{month:00}{day:00}"; + var version = $"{Utils.RandomInt(1, 15)}.0"; + + return $"Mozillia/5.0 ({os}; rv:{version}) Gecko/{gecko} Firefox/{version}"; + } + if (browser == "IE") + { + var version = $"{Utils.RandomInt(1, 10)}.0"; + var engine = $"{Utils.RandomInt(1, 5)}.0"; + var option = Utils.RandomBool(); + string token; + if (option) + { + var v = new [] { ".NET CLR", "SV1", "Tablet PC", "Win64; IA64", "Win64; x64", "WOW64" }.TakeRandom(); + token = $"{v};"; + } + else + token = ""; + + return $"Mozilla/5.0 (compatible; MSIE {version}; {os}; {token}Trident/{engine})"; + } + + throw new NotImplementedException(); + } } -} \ No newline at end of file +} diff --git a/PriceChecker.Core/Utils.cs b/PriceChecker.Core/Utils.cs new file mode 100644 index 0000000..4acfb74 --- /dev/null +++ b/PriceChecker.Core/Utils.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Genius.PriceChecker.Core +{ + public static class Utils + { + private static Random _rnd = new(); + + public static bool RandomBool() + => _rnd.NextDouble() >= 0.5; + + public static int RandomInt(int from, int to) + => _rnd.Next(from, to); + + public static T TakeRandom(this IList list) + { + if (list.Count == 0) + return default (T); + return list[_rnd.Next(0, list.Count - 1)]; + } + } +} \ No newline at end of file diff --git a/PriceChecker.UI/ViewModels/TrackerProductViewModel.cs b/PriceChecker.UI/ViewModels/TrackerProductViewModel.cs index 01a87d0..8bbcd13 100644 --- a/PriceChecker.UI/ViewModels/TrackerProductViewModel.cs +++ b/PriceChecker.UI/ViewModels/TrackerProductViewModel.cs @@ -163,7 +163,9 @@ private void CommitProduct() private TrackerProductSourceViewModel CreateSourceViewModel(ProductSource productSource) { - var lastPrice = _product?.Recent?.FirstOrDefault(x => x.ProductSourceId == productSource.Id)?.Price; + var lastPrice = productSource == null || _product?.Recent == null + ? null + : _product.Recent.FirstOrDefault(x => x.ProductSourceId == productSource.Id)?.Price; var vm = new TrackerProductSourceViewModel(_ui, productSource, lastPrice); vm.DeleteCommand.Executed += (_, __) => { Sources.Remove(vm); diff --git a/README.md b/README.md index 0522107..dcaeb5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # price-checker A simple price checker for the selected products to catch the cheapest deal + +### Main window: +![Main window](docs/assets/MainWindow.png) + +### Product edit: +![Product edit](docs/assets/ProductEdit.png) + +### Product with updated lowest price: +![Product with updated lowest price](docs/assets/ProductWithUpdatedLowestPrice.png) diff --git a/agents.json b/agents.json index 4bb82ba..44c545c 100644 --- a/agents.json +++ b/agents.json @@ -40,11 +40,5 @@ "Url": "https://www.viofo.nl/en/collections/viofo-dashcams/products/{0}", "PricePattern": "\u0022og:price:amount\u0022 content=\u0022(?\u003Cprice\u003E[\\d,]\u002B)\u0022\u003E", "DecimalDelimiter": "," - }, - { - "Id": "t1", - "Url": "t2", - "PricePattern": "t3", - "DecimalDelimiter": "." } ] \ No newline at end of file diff --git a/docs/assets/MainWindow.png b/docs/assets/MainWindow.png new file mode 100644 index 0000000..5271811 Binary files /dev/null and b/docs/assets/MainWindow.png differ diff --git a/docs/assets/ProductEdit.png b/docs/assets/ProductEdit.png new file mode 100644 index 0000000..2f5913d Binary files /dev/null and b/docs/assets/ProductEdit.png differ diff --git a/docs/assets/ProductWithUpdatedLowestPrice.png b/docs/assets/ProductWithUpdatedLowestPrice.png new file mode 100644 index 0000000..c0aaecb Binary files /dev/null and b/docs/assets/ProductWithUpdatedLowestPrice.png differ diff --git a/products.json b/products.json index 1104445..fe0ba03 100644 --- a/products.json +++ b/products.json @@ -13,14 +13,14 @@ ], "Lowest": { "ProductSourceId": "60b9ceed-b34c-45ad-a142-af0ec5491d91", - "Price": 22.90, - "FoundDate": "2021-06-08T10:05:07.2366544+02:00" + "Price": 21.50, + "FoundDate": "2021-06-09T11:37:07.6430411+02:00" }, "Recent": [ { "ProductSourceId": "60b9ceed-b34c-45ad-a142-af0ec5491d91", - "Price": 22.90, - "FoundDate": "2021-06-08T10:05:07.2366544+02:00" + "Price": 21.50, + "FoundDate": "2021-06-09T11:37:07.6430411+02:00" } ] }, @@ -48,14 +48,14 @@ }, "Recent": [ { - "ProductSourceId": "4753f887-5b20-41de-a06b-3c159262a273", - "Price": 799, - "FoundDate": "2021-06-08T10:06:20.3313322+02:00" + "ProductSourceId": "ebeaa98d-aedb-4bb8-b539-1de7e1ee25cc", + "Price": 779, + "FoundDate": "2021-06-09T11:37:06.9271835+02:00" }, { - "ProductSourceId": "ebeaa98d-aedb-4bb8-b539-1de7e1ee25cc", - "Price": 756, - "FoundDate": "2021-06-08T10:06:20.3313476+02:00" + "ProductSourceId": "4753f887-5b20-41de-a06b-3c159262a273", + "Price": 799, + "FoundDate": "2021-06-09T11:37:06.9271872+02:00" } ] }, @@ -84,23 +84,23 @@ "Lowest": { "ProductSourceId": "59b2149f-9148-40eb-88c3-16061247487f", "Price": 659.99, - "FoundDate": "2021-06-08T10:06:38.7756063+02:00" + "FoundDate": "2021-06-09T11:37:18.9777833+02:00" }, "Recent": [ { "ProductSourceId": "59b2149f-9148-40eb-88c3-16061247487f", "Price": 659.99, - "FoundDate": "2021-06-08T10:06:38.7756063+02:00" + "FoundDate": "2021-06-09T11:37:18.9777833+02:00" }, { "ProductSourceId": "190b93f8-d428-4b9b-aa70-127c513d352f", "Price": 699, - "FoundDate": "2021-06-08T10:06:38.7756111+02:00" + "FoundDate": "2021-06-09T11:37:18.9777865+02:00" }, { "ProductSourceId": "7abd51a9-7ba1-4b72-9ff3-3fb89fce9045", "Price": 729.00, - "FoundDate": "2021-06-08T10:06:38.775613+02:00" + "FoundDate": "2021-06-09T11:37:18.9777868+02:00" } ] }, @@ -129,23 +129,23 @@ "Lowest": { "ProductSourceId": "3b27b7fc-369d-4f90-ba4c-f209c1a0153b", "Price": 165.95, - "FoundDate": "2021-06-08T10:06:25.8162775+02:00" + "FoundDate": "2021-06-09T11:37:11.7831872+02:00" }, "Recent": [ { "ProductSourceId": "d786a796-b93c-468b-8fda-a2892d72802a", "Price": 179.0, - "FoundDate": "2021-06-08T10:06:25.8162724+02:00" - }, - { - "ProductSourceId": "1778e5f5-b73d-418b-b3b8-06412828f54f", - "Price": 169.00, - "FoundDate": "2021-06-08T10:06:25.816277+02:00" + "FoundDate": "2021-06-09T11:37:11.7831838+02:00" }, { "ProductSourceId": "3b27b7fc-369d-4f90-ba4c-f209c1a0153b", "Price": 165.95, - "FoundDate": "2021-06-08T10:06:25.8162775+02:00" + "FoundDate": "2021-06-09T11:37:11.7831872+02:00" + }, + { + "ProductSourceId": "1778e5f5-b73d-418b-b3b8-06412828f54f", + "Price": 168.00, + "FoundDate": "2021-06-09T11:37:11.7831876+02:00" } ] }, @@ -168,19 +168,19 @@ ], "Lowest": { "ProductSourceId": "923b83ed-3fcb-43ca-80b0-0e9e717ec9c6", - "Price": 429.9, - "FoundDate": "2021-06-08T10:06:27.8310704+02:00" + "Price": 429.0, + "FoundDate": "2021-06-09T11:37:13.8349092+02:00" }, "Recent": [ { "ProductSourceId": "923b83ed-3fcb-43ca-80b0-0e9e717ec9c6", - "Price": 429.9, - "FoundDate": "2021-06-08T10:06:27.8310704+02:00" + "Price": 429.0, + "FoundDate": "2021-06-09T11:37:13.8349092+02:00" }, { "ProductSourceId": "5f5a72a4-d6ad-4cab-9fb9-9d5839fa2a03", "Price": 449.95, - "FoundDate": "2021-06-08T10:06:27.8310749+02:00" + "FoundDate": "2021-06-09T11:37:13.8349127+02:00" } ] }, @@ -204,18 +204,18 @@ "Lowest": { "ProductSourceId": "62dcd317-1805-4b07-bb30-81045d4a6517", "Price": 179.95, - "FoundDate": "2021-06-08T10:04:47.5658818+02:00" + "FoundDate": "2021-06-09T11:37:08.9364614+02:00" }, "Recent": [ { "ProductSourceId": "62dcd317-1805-4b07-bb30-81045d4a6517", "Price": 179.95, - "FoundDate": "2021-06-08T10:04:47.5658818+02:00" + "FoundDate": "2021-06-09T11:37:08.9364614+02:00" }, { "ProductSourceId": "76ba5029-6cf6-4231-bc99-ca7d65cde50d", "Price": 189.95, - "FoundDate": "2021-06-08T10:04:47.5658958+02:00" + "FoundDate": "2021-06-09T11:37:08.9364653+02:00" } ] }, @@ -245,12 +245,7 @@ { "ProductSourceId": "cbb43572-35b9-4408-9e52-09f934b97336", "Price": 589.55, - "FoundDate": "2021-06-04T18:57:59.3983603+02:00" - }, - { - "ProductSourceId": "ab80a6f9-c175-4c10-8f69-aee23e7efc64", - "Price": 442.47, - "FoundDate": "2021-06-04T18:57:59.398364+02:00" + "FoundDate": "2021-06-08T15:46:18.7308128+02:00" } ] }, @@ -264,6 +259,11 @@ "Id": "aa039437-a86f-4cf7-9283-90c63a95e142", "AgentId": "amazon.de", "AgentArgument": "B01K4CYV58" + }, + { + "Id": "b129f9f2-416b-493e-af3c-ff0f255a66e9", + "AgentId": "tweakers.net", + "AgentArgument": "580883/garmin-virb-ultra-30-rood-zwart.html" } ], "Lowest": { @@ -273,9 +273,9 @@ }, "Recent": [ { - "ProductSourceId": "aa039437-a86f-4cf7-9283-90c63a95e142", - "Price": 389.95, - "FoundDate": "2021-06-04T16:23:06.2236865+02:00" + "ProductSourceId": "b129f9f2-416b-493e-af3c-ff0f255a66e9", + "Price": 499.99, + "FoundDate": "2021-06-09T11:42:53.9223031+02:00" } ] }, @@ -294,13 +294,13 @@ "Lowest": { "ProductSourceId": "831217bc-0784-4d57-a6a5-d71bf6b2bd19", "Price": 236.80, - "FoundDate": "2021-06-04T16:36:05.7758763+02:00" + "FoundDate": "2021-06-08T15:46:18.4488366+02:00" }, "Recent": [ { "ProductSourceId": "831217bc-0784-4d57-a6a5-d71bf6b2bd19", "Price": 236.80, - "FoundDate": "2021-06-04T16:36:05.7758763+02:00" + "FoundDate": "2021-06-08T15:46:18.4488366+02:00" } ] }, @@ -319,13 +319,13 @@ "Lowest": { "ProductSourceId": "65f0f16f-a64c-4244-bd3b-ea58c27740ed", "Price": 349.99, - "FoundDate": "2021-06-08T10:12:50.4494975+02:00" + "FoundDate": "2021-06-08T15:46:18.1966565+02:00" }, "Recent": [ { "ProductSourceId": "65f0f16f-a64c-4244-bd3b-ea58c27740ed", "Price": 349.99, - "FoundDate": "2021-06-08T10:12:50.4494975+02:00" + "FoundDate": "2021-06-08T15:46:18.1966565+02:00" } ] }, @@ -349,8 +349,8 @@ "Recent": [ { "ProductSourceId": "c33f08c8-854e-4a60-8eff-bd900cffe134", - "Price": 279.99, - "FoundDate": "2021-06-04T18:18:42.7704614+02:00" + "Price": 349.0, + "FoundDate": "2021-06-08T15:46:22.6356321+02:00" } ] }, @@ -374,8 +374,8 @@ "Recent": [ { "ProductSourceId": "64f76755-0839-4c6c-9a7c-e28e4cf1e670", - "Price": 298.44, - "FoundDate": "2021-06-04T18:20:57.556876+02:00" + "Price": 299.99, + "FoundDate": "2021-06-08T15:46:24.7254817+02:00" } ] }, @@ -389,18 +389,28 @@ "Id": "e4b4c4d2-d318-4304-af16-3b97a18509f7", "AgentId": "amazon.de", "AgentArgument": "B08G2HBBB6" + }, + { + "Id": "05b5f419-0ace-4ba7-b35a-4eb571c5ecc6", + "AgentId": "tweakers.net", + "AgentArgument": "1611988/gopro-hero9-black-zwart.html" } ], "Lowest": { "ProductSourceId": "e4b4c4d2-d318-4304-af16-3b97a18509f7", "Price": 399.99, - "FoundDate": "2021-06-04T18:22:51.7624875+02:00" + "FoundDate": "2021-06-09T11:44:49.8138826+02:00" }, "Recent": [ + { + "ProductSourceId": "05b5f419-0ace-4ba7-b35a-4eb571c5ecc6", + "Price": 409, + "FoundDate": "2021-06-09T11:44:49.813879+02:00" + }, { "ProductSourceId": "e4b4c4d2-d318-4304-af16-3b97a18509f7", "Price": 399.99, - "FoundDate": "2021-06-04T18:22:51.7624875+02:00" + "FoundDate": "2021-06-09T11:44:49.8138826+02:00" } ] }, @@ -419,13 +429,13 @@ "Lowest": { "ProductSourceId": "e593c859-0dff-491d-a9ad-1a2f74a7f6f6", "Price": 258.99, - "FoundDate": "2021-06-04T18:24:14.2715873+02:00" + "FoundDate": "2021-06-08T15:46:28.6875693+02:00" }, "Recent": [ { "ProductSourceId": "e593c859-0dff-491d-a9ad-1a2f74a7f6f6", "Price": 258.99, - "FoundDate": "2021-06-04T18:24:14.2715873+02:00" + "FoundDate": "2021-06-08T15:46:28.6875693+02:00" } ] }