private void deleteItemButton_Click(object sender, EventArgs e) { var iCellIndex = wantsView.Columns["idWant"].Index; if (wantsView.SelectedRows.Count == 0) { MessageBox.Show("Please select the row you want to delete!"); return; } foreach (DataGridViewRow selectedRow in wantsView.SelectedRows) { string idWant = selectedRow.Cells[iCellIndex].Value.ToString(); string sRequestXML = MKMInteract.RequestHelper.deleteWantsListBody(idWant); sRequestXML = MKMInteract.RequestHelper.getRequestBody(sRequestXML); string sListId = (wantListsBox.SelectedItem as MKMHelpers.ComboboxItem).Value.ToString(); try { MKMInteract.RequestHelper.makeRequest("https://api.cardmarket.com/ws/v2.0/wantslist/" + sListId, "PUT", sRequestXML); } catch (Exception eError) { MKMHelpers.LogError("deleting from wantlist item #" + idWant, eError.Message, true, sRequestXML); } } wantsListBoxReload(); }
public MainView() { InitializeComponent(); #if DEBUG logBox.AppendText("DEBUG MODE ON!\n"); #endif try { if (!File.Exists(@".\\config.xml")) { MessageBox.Show("No config file found! Create a config.xml first."); Application.Exit(); } MKMHelpers.GetProductList(); var doc2 = MKMInteract.RequestHelper.getAccount(); MKMHelpers.sMyOwnCountry = doc2["response"]["account"]["country"].InnerText; MKMHelpers.sMyId = doc2["response"]["account"]["idUser"].InnerText; bot = new MKMBot(); } catch (Exception eError) { MessageBox.Show(eError.Message); } }
/// <summary> /// Loads all setting presets stored as .xml files in /Presets/ folder /// and populates the combobox with their names. /// </summary> private void loadPresets() { DirectoryInfo d = new DirectoryInfo(@".//Presets"); FileInfo[] Files = d.GetFiles("*.xml"); presets = new Dictionary <string, MKMBotSettings>(); foreach (FileInfo file in Files) { MKMBotSettings s = new MKMBotSettings(); try { XmlDocument doc = new XmlDocument(); doc.Load(file.FullName); s.Parse(doc); string name = file.Name.Substring(0, file.Name.Length - 4); // cut off the ".xml" presets[name] = s; comboBoxPresets.Items.Add(name); } catch (Exception eError) { MKMHelpers.LogError("reading preset " + file.Name, eError.Message, false); } } comboBoxPresets.SelectedIndex = comboBoxPresets.Items.Add("Choose Preset..."); }
private void addButton_Click(object sender, EventArgs e) { foreach (ListViewItem item in cardView.SelectedItems) { var idProduct = item.Text; //MessageBox.Show(idProduct); //addWantsListBody(string idProduct, string minCondition, string idLanguage, string isFoil, string isAltered, string isPlayset, string isSigned) var sRequestXML = MKMInteract.RequestHelper.addWantsListBody(idProduct, conditionCombo.Text, (langCombo.SelectedItem as MKMHelpers.ComboboxItem).Value.ToString(), foilBox.CheckState.ToString(), alteredBox.CheckState.ToString(), playsetBox.CheckState.ToString(), signedBox.CheckState.ToString()); sRequestXML = MKMInteract.RequestHelper.getRequestBody(sRequestXML); try { var sListId = (wantListsBox.SelectedItem as MKMHelpers.ComboboxItem).Value.ToString(); MKMInteract.RequestHelper.makeRequest("https://api.cardmarket.com/ws/v2.0/wantslist/" + sListId, "PUT", sRequestXML); } catch (Exception eError) { MKMHelpers.LogError("adding to wantlist, product id " + idProduct, eError.Message, true, sRequestXML); return; } } wantsListBoxReload(); }
private void buttonPresetsDelete_Click(object sender, EventArgs e) { string name = comboBoxPresets.SelectedItem.ToString(); if (MessageBox.Show("Are you sure you want to delete preset '" + name + "'? This cannot be undone.", "Delete preset", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { try { FileInfo f = new FileInfo(@".//Presets//" + name + ".xml"); if (f.Exists) // better check, could have been deleted manually during the run { f.Delete(); } presets.Remove(name); comboBoxPresets.Items.RemoveAt(comboBoxPresets.SelectedIndex); comboBoxPresets.SelectedIndex = comboBoxPresets.Items.Add("Choose Preset..."); labelPresetsDescr.Text = ""; } catch (Exception exc) { MKMHelpers.LogError("deleting preset", exc.Message, true); } } }
public void initWantLists() { XmlDocument doc = null; try { doc = MKMInteract.RequestHelper.getWantsLists(); } catch (Exception eError) { MKMHelpers.LogError("initializing want list, editor will be disabled", eError.Message, true); addButton.Enabled = false; deleteItemButton.Enabled = false; return; } var node = doc.GetElementsByTagName("wantslist"); if (node.Count > 0) { wantListsBox.Items.Clear(); foreach (XmlNode nWantlist in node) { var item = new MKMHelpers.ComboboxItem(); item.Text = nWantlist["name"].InnerText; item.Value = nWantlist["idWantslist"].InnerText; wantListsBox.Items.Add(item); wantListsBox.SelectedIndex = 0; } } }
public DataTable buildProperWantsList(string sListId) { try { var doc = MKMInteract.RequestHelper.getWantsListByID(sListId); var xmlReader = new XmlNodeReader(doc); var ds = new DataSet(); ds.ReadXml(xmlReader); if (!ds.Tables.Contains("item")) { return(new DataTable()); } DataTable eS = MKMHelpers.ReadSQLiteToDt("expansions"); var dv = MKMHelpers.JoinDataTables(dt, eS, (row1, row2) => row1.Field <string>("Expansion ID") == row2.Field <string>("idExpansion")); dv = MKMHelpers.JoinDataTables(dv, ds.Tables["item"], (row1, row2) => row1.Field <string>("idProduct") == row2.Field <string>("idProduct")); return(dv); } catch (Exception eError) { MessageBox.Show(eError.ToString()); return(new DataTable()); } }
/// Uses the provided price guides to compute the price according to the formula. /// <param name="priceGuides">Row from the priceGuides table containing guides for the card for which to evaluate this formula. /// If this formula UsesPriceGuides, must be set to contain all the columns, otherwise exception is thrown.</param> /// <param name="currentPrice">Our current sale price of the card (can be used as a guide).</param> /// <returns>The final price this formula evaluates to. NaN if the necessary guides are not found.</returns> public double Evaluate(DataRow priceGuides, double currentPriceSingle) { double term; foreach (var guideTerm in guidesToResolve) { if (guideTerm.Value == "CURRENT") { term = currentPriceSingle; } else { term = MKMHelpers.ConvertDoubleAnySep(priceGuides[guideTerm.Value].ToString()); } if (double.IsNaN(term)) { return(term); } operands[guideTerm.Key] = term; } double val = operands[0]; for (int i = 0; i < operators.Count; i++) { val = operators[i](val, operands[i + 1]); } return(val); }
/// <summary> /// Initializes the instance of this MainView. /// Because error logging mechanism uses the MainView's console, it needs to be called only after the handle for the window /// has been created --> during the "Load" event or later (after the form has been created and shown). /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> private void initialize(object sender, EventArgs e) { Config = new MKMToolConfig(); timer.Interval = 1440 * 1000 * 60; // set the interval to one day (1440 minutes in ms) try { var doc2 = MKMInteract.RequestHelper.getAccount(); if (Config.MyCountryCode == "") // signifies user wants auto-detect, otherwise the chosen setting overrides what we get from MKM { Config.MyCountryCode = doc2["response"]["account"]["country"].InnerText; } MKMHelpers.sMyId = doc2["response"]["account"]["idUser"].InnerText; } catch (Exception eError) { MKMHelpers.LogError("initializing product list and account info", eError.Message, true); } bot = new MKMBot(); settingsWindow = new UpdatePriceSettings("LastSettingsPreset", "Settings of Update Price"); stockViewWindow = new StockView(); checkCheapDealsWindow = new CheckWantsView(); checkDisplayPricesWindow = new CheckDisplayPrices(); wantlistEditorViewWindow = new WantlistEditorView(); priceExternalListWindow = new PriceExternalList(); }
public void initCardView() { try { cardView.View = View.Details; cardView.GridLines = true; cardView.FullRowSelect = true; cardView.BackColor = Color.WhiteSmoke; cardView.Columns.Add("ProduktID", 60); cardView.Columns.Add("Card Name", 220); cardView.Columns.Add("Edition", 120); dj = new DataTable(); dj = MKMHelpers.JoinDataTables(dt, eS, (row1, row2) => row1.Field <string>("Expansion ID") == row2.Field <string>("idExpansion")); foreach (DataRow row in dj.Rows) { var item = new ListViewItem(row["idProduct"].ToString()); item.SubItems.Add(row["Name"].ToString()); item.SubItems.Add(row["enName"].ToString()); cardView.Items.Add(item); } } catch (Exception eError) { MessageBox.Show(eError.ToString()); } }
public void initWantLists() { XmlDocument doc = null; try { doc = MKMInteract.RequestHelper.getWantsLists(); } catch (Exception eError) { MKMHelpers.LogError("fetching all want lists, disabling Check Wantlist", eError.Message, true); checkListButton.Enabled = false; return; } var node = doc.GetElementsByTagName("wantslist"); foreach (XmlNode nWantlist in node) { var item = new MKMHelpers.ComboboxItem(); item.Text = nWantlist["name"].InnerText; item.Value = nWantlist["idWantslist"].InnerText; wantListsBox2.Items.Add(item); wantListsBox2.SelectedIndex = 0; } }
/// <summary> /// Sends stock update to MKM. /// </summary> /// <param name="sRequestXML">The raw (= not as an API request yet) XML with all article updates. Check that it is not empty before calling this. /// Also, it must match the used method - PUT requires to know articleID for each article, POST productID.</param> /// <param name="method">"PUT" to update articles already in stock, "POST" to upload new articles</param> public static void SendStockUpdate(string sRequestXML, string method) { sRequestXML = getRequestBody(sRequestXML); try { XmlDocument rdoc = makeRequest("https://api.cardmarket.com/ws/v2.0/stock", method, sRequestXML); int iUpdated = 0, iFailed = 0; if (method == "PUT") { var xUpdatedArticles = rdoc.GetElementsByTagName("updatedArticles"); var xNotUpdatedArticles = rdoc.GetElementsByTagName("notUpdatedArticles"); iUpdated = xUpdatedArticles.Count; iFailed = xNotUpdatedArticles.Count; } else if (method == "POST") { XmlNodeList inserted = rdoc.GetElementsByTagName("inserted"); foreach (XmlNode x in inserted) { if (x["success"].InnerText == "true") { iUpdated++; } else { iFailed++; } } } MainView.Instance.LogMainWindow( iUpdated + " articles updated successfully, " + iFailed + " failed"); if (iFailed > 0) { try { File.WriteAllText(@".\\log" + DateTime.Now.ToString("ddMMyyyy-HHmm") + ".log", rdoc.InnerText.ToString()); } catch (Exception eError) { MKMHelpers.LogError("logging failed stock update articles", eError.Message, false); } MainView.Instance.LogMainWindow( "Failed articles logged in " + @".\\log" + DateTime.Now.ToString("ddMMyyyy-HHmm") + ".log"); } } catch (Exception eError) { // for now this does not break the price update, i.e. the bot will attempt to update the following items - maybe it shouldn't as it is likely to fail again? MKMHelpers.LogError("sending stock update to MKM", eError.Message, false, sRequestXML); } }
private void button1_Click(object sender, EventArgs e) { try { MKMInteract.RequestHelper.emptyCart(); MainView.Instance.LogMainWindow("Shopping Cart emptied."); } catch (Exception eError) { MKMHelpers.LogError("emptying shopping cart, cart not emptied", eError.Message, true); } }
public StockView() { InitializeComponent(); try { eS = MKMHelpers.ReadSQLiteToDt("expansions"); stockGridView.ReadOnly = true; } catch (Exception eError) { MessageBox.Show(eError.ToString()); } try { var doc = MKMInteract.RequestHelper.readStock(); var xmlReader = new XmlNodeReader(doc); var ds = new DataSet(); ds.ReadXml(xmlReader); var dj = MKMHelpers.JoinDataTables(ds.Tables[0], dt, (row1, row2) => row1.Field <string>("idProduct") == row2.Field <string>("idProduct")); dj = MKMHelpers.JoinDataTables(dj, eS, (row1, row2) => row1.Field <string>("Expansion ID") == row2.Field <string>("idExpansion")); dj.Columns.Remove("article_Id"); dj.Columns.Remove("Date Added"); dj.Columns.Remove("Category ID"); dj.Columns.Remove("Category"); dj.Columns.Remove("Metacard ID"); dj.Columns.Remove("idArticle"); dj.Columns.Remove("idProduct"); dj.Columns.Remove("Expansion ID"); dj.Columns.Remove("idExpansion"); dj.Columns[dj.Columns.IndexOf("Name")].SetOrdinal(0); stockGridView.DataSource = dj; //dataGridView1.DataSource = dt; } catch (Exception eError) { MessageBox.Show(eError.ToString()); } }
private void button1_Click(object sender, EventArgs e) { string searchString = searchBox.Text.Replace("'", ""); try { (stockGridView.DataSource as DataTable).DefaultView.RowFilter = string.Format("Name LIKE '%{0}%'", searchString); } catch (Exception eError) { MKMHelpers.LogError("searching for " + searchString + " in Stock View", eError.Message, true); } }
/// <summary> /// Initializes the instance of this MainView. /// Because error logging mechanism uses the MainView's console, it needs to be called only after the handle for the window /// has been created --> during the "Load" event or later (after the form has been created and shown). /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> private void initialize(object sender, EventArgs e) { timer.Interval = 1440 * 1000 * 60; // set the interval to one day (1440 minutes in ms) try { var doc2 = MKMInteract.RequestHelper.getAccount(); MKMHelpers.sMyOwnCountry = doc2["response"]["account"]["country"].InnerText; MKMHelpers.sMyId = doc2["response"]["account"]["idUser"].InnerText; } catch (Exception eError) { MKMHelpers.LogError("initializing product list and account info", eError.Message, true); } bot = new MKMBot(); }
/// Sets this from string. /// <param name="formula">The formula as string.</param> /// <returns>True if parsing finished successfully, false if some of the operands were neither numbers nor price guides.</returns> public bool Parse(string formula) { string formulaOrig = formula; formula = formula.Trim(); var separators = new char[] { '*', '-', '+' }; // parse the first operand, there must be at least one and the formula cannot start with an operand string operand = formula.Split(separators, 2)[0]; if (!parseAndAddOperand(operand.Trim())) { MKMHelpers.LogError("parsing price formula", "failed to parse operand " + operand + " in formula " + formulaOrig + ", it cannot be used.", true); return(false); } formula = formula.Substring(operand.Length); while (formula.Length > 0) { char oper = formula[0]; // the next symbol is surely the operand switch (oper) { case '*': operators.Add(mult); break; case '-': operators.Add(subtract); break; case '+': operators.Add(adding); break; } formula = formula.Substring(1); // there must be another operand after operator, otherwise it is an error operand = formula.Split(separators, 2)[0]; if (!parseAndAddOperand(operand.Trim())) { MKMHelpers.LogError("parsing price formula", "failed to parse operand " + operand + " in formula " + formulaOrig + ", it cannot be used.", false); return(false); } formula = formula.Substring(operand.Length); formula.Trim(); } return(true); }
public AccountInfo() { InitializeComponent(); treeView1.Nodes.Clear(); treeView1.Nodes.Add(new TreeNode("Account Details")); var tNode = treeView1.Nodes[0]; try { var doc = MKMInteract.RequestHelper.GetAccount(); addNode(doc["response"]["account"], tNode); } catch (System.Exception eError) { MKMHelpers.LogError("fetching account info", eError.Message, true); } //treeView1.ExpandAll(); }
public static byte[] GetPriceGuideFile(string gameId) { var doc = MakeRequest("https://api.cardmarket.com/ws/v2.0/priceguide?idGame=" + gameId, "GET"); var node = doc.GetElementsByTagName("response"); if (node.Count > 0 && node.Item(0)["priceguidefile"].InnerText != null) { var data = Convert.FromBase64String(node.Item(0)["priceguidefile"].InnerText); var aDecompressed = MKMHelpers.GzDecompress(data); return(aDecompressed); } else { MKMHelpers.LogError("getting price guide file", "failed to get the price guide file from MKM.", false); return(null); } }
// this is done whenever it is shown/hidden - in case it is made visible, reload all data in case something has changed in the meantime private void CheckWantsView_VisibleChanged(object sender, EventArgs e) { if (Visible) { MKMDbManager.Instance.PopulateExpansionsComboBox(editionBox); if (editionBox.Items.Count > 0) { editionBox.Sorted = true; editionBox.SelectedIndex = 0; } else { MKMHelpers.LogError("loading expansions from local database for Check Cheap Deals", "Database empty.", false); } conditionCombo.SelectedIndex = 4; initWantLists(); } }
/// Converts the operand to number or price guide and stores it /// <param name="operand">Trimmed string representation of the operand.</param> /// <returns>False if the parsing failed.</returns> private bool parseAndAddOperand(string operand) { double number = MKMHelpers.ConvertDoubleAnySep(operand); if (double.IsNaN(number)) { foreach (var guide in MKMHelpers.PriceGuides) { if (guide.Value.Code == operand) { guidesToResolve.Add(new KeyValuePair <int, string>(operands.Count, operand)); operands.Add(-9999); return(true); } } } else { operands.Add(number); return(true); } return(false); }
public MKMToolConfig() { var xConfigFile = new XmlDocument(); xConfigFile.Load(@".//config.xml"); if (xConfigFile["config"]["settings"] != null) { var settings = xConfigFile["config"]["settings"]; foreach (XmlNode setting in settings) { switch (setting.Name) { case "UseStockGetFile": if (bool.TryParse(setting.InnerText, out bool stockGetMethod)) { UseStockGetFile = stockGetMethod; } break; case "Games": var split = setting.InnerText.Split(';'); if (split.Length == 0) { MKMHelpers.LogError("reading Games setting", "No games specified in config.xml.", false); } else { Games.Clear(); foreach (var game in split) { var trimmed = game.Trim(); if (trimmed.Length > 0) { if (MKMHelpers.gameIDsByName.ContainsKey(trimmed)) { Games.Add(MKMHelpers.gameIDsByName[trimmed]); } else { MKMHelpers.LogError("reading Games setting", "Unknown game " + trimmed + " specified, will be ignored.", false); } } } } break; case "CSVExportSeparator": CSVExportSeparator = setting.InnerText; break; case "MyCountryCode": if (setting.InnerText != "" && MKMHelpers.countryNames.ContainsKey(setting.InnerText)) { MyCountryCode = setting.InnerText; } // else leave the default, i.e. "" break; } } } }
// reload data each time the form is made visible in case the user's stock has changed so they can reload the stockview this way private void stockView_VisibleChanged(object sender, EventArgs e) { if (Visible) { var articles = new DataTable(); try { // getAllStockSingles creates a DataTable and converts it to list of cards, so theoretically we are wasting some work // but it also filters out non-singles and converting to MKMcard will make sure we use the primary column names rather than synonyms var cards = MKMInteract.RequestHelper.GetAllStockSingles(MainView.Instance.Config.UseStockGetFile); if (cards.Count == 0) { MainView.Instance.LogMainWindow("Stock is empty. Did you select correct idGame in config.xml?"); return; } foreach (var card in cards) { card.WriteItselfIntoTable(articles, true, MCFormat.MKM, true); } // Remove columns we don't want showing // TODO - what is and isn't shown should probably be customizable and left to the user to choose in some way if (articles.Columns.Contains(MCAttribute.ArticleID)) { articles.Columns.Remove(MCAttribute.ArticleID); } if (articles.Columns.Contains(MCAttribute.LanguageID)) { articles.Columns.Remove(MCAttribute.LanguageID); } if (articles.Columns.Contains(MCAttribute.MetaproductID)) { articles.Columns.Remove(MCAttribute.MetaproductID); } if (articles.Columns.Contains("onSale")) { articles.Columns.Remove("onSale"); // don't even know what this one is supposed to be, it's not even in the API documentation } if (articles.Columns.Contains(MCAttribute.MKMCurrencyCode)) { articles.Columns.Remove(MCAttribute.MKMCurrencyCode); } if (articles.Columns.Contains(MCAttribute.MKMCurrencyId)) { articles.Columns.Remove(MCAttribute.MKMCurrencyId); } var dj = MKMDbManager.JoinDataTables(articles, MKMDbManager.Instance.Expansions, (row1, row2) => row1.Field <string>(MCAttribute.ExpansionID) == row2.Field <string>(MKMDbManager.ExpansionsFields.ExpansionID)); if (dj.Columns.Contains(MCAttribute.ExpansionID)) { dj.Columns.Remove(MCAttribute.ExpansionID); // duplicated } if (dj.Columns.Contains(MKMDbManager.ExpansionsFields.ExpansionID)) { dj.Columns.Remove(MKMDbManager.ExpansionsFields.ExpansionID); // ...and we don't want it anyway } if (dj.Columns.Contains(MKMDbManager.ExpansionsFields.Name)) { dj.Columns.Remove(MKMDbManager.ExpansionsFields.Name); // duplicated } // use the same order with or without UseStockGetFile System.Collections.Generic.List <string> attsOrdered = new System.Collections.Generic.List <string> { MCAttribute.Name, MCAttribute.Expansion, MCAttribute.Language, MCAttribute.ProductID, MCAttribute.MKMPrice, MCAttribute.Condition, MCAttribute.Comments, MCAttribute.Foil, MCAttribute.Altered, MCAttribute.Signed, MCAttribute.Playset, MCAttribute.Count, MCAttribute.Rarity, MCAttribute.ExpansionAbb }; int ordinal = 0; foreach (string att in attsOrdered) { if (dj.Columns.Contains(att)) { dj.Columns[dj.Columns.IndexOf(att)].SetOrdinal(ordinal++); } } // convert columns with numerical data from string so that sorting works correctly if (dj.Columns.Contains(MCAttribute.ProductID)) { convertNumberColumn(dj, MCAttribute.ProductID, false); } if (dj.Columns.Contains(MCAttribute.Count)) { convertNumberColumn(dj, MCAttribute.Count, false); } if (dj.Columns.Contains(MCAttribute.MKMPrice)) { convertNumberColumn(dj, MCAttribute.MKMPrice, true); } stockGridView.DataSource = dj; buttonExport.Enabled = true; } catch (Exception eError) { MKMHelpers.LogError("listing stock in Stock View", eError.Message, true); } } }
private void wantsListBoxReload() { var sListId = (wantListsBox.SelectedItem as MKMHelpers.ComboboxItem).Value.ToString(); wantsView.Columns.Clear(); DataTable dv = null; try { var doc = MKMInteract.RequestHelper.getWantsListByID(sListId); var xmlReader = new XmlNodeReader(doc); var ds = new DataSet(); ds.ReadXml(xmlReader); if (!ds.Tables.Contains("item")) { return; } dv = ds.Tables["item"]; dv.Columns.Add("enName"); dv.Columns.Add("expansionName"); dv.Columns["enName"].SetOrdinal(0); // put the name and expansion at the beggining of the table dv.Columns["expansionName"].SetOrdinal(1); int itemCounter = 0; foreach (XmlNode child in doc["response"]["wantslist"].ChildNodes) { if (child.Name == "item") { if (child["type"].InnerText == "product") { dv.Rows[itemCounter]["enName"] = child["product"]["enName"].InnerText; dv.Rows[itemCounter]["expansionName"] = child["product"]["expansionName"].InnerText; } else // it is a metaproduct { dv.Rows[itemCounter]["enName"] = child["metaproduct"]["enName"].InnerText; // we don't care which child is the first, the name is the same for all // we would have to do an additional API call to get the list of expansions // given that the current code refreshes this view everytime an item is added to wantlist, it is not very practical dv.Rows[itemCounter]["expansionName"] = "<any>"; } itemCounter++; } } dv = ds.Tables["item"]; } catch (Exception eError) { MKMHelpers.LogError("processing wantlist " + sListId + ", it will be ignored", eError.Message, true); return; } if (dv.Select().Length > 0) { wantsView.AutoGenerateColumns = true; wantsView.DataSource = dv; wantsView.Refresh(); if (wantsView.Columns.Contains("idProduct")) // if the wantlist has only metaproducts, there will be no idProduct entry { wantsView.Columns["idProduct"].Visible = false; } wantsView.Columns["idWant"].Visible = false; wantsView.Columns["type"].Visible = false; wantsView.Columns["wishPrice"].Visible = false; wantsView.Columns["count"].Visible = false; wantsView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; wantsView.ReadOnly = true; } }
// idArticle - if not empty, article will be added to shopping cart only if it is matching the specified idArticle. used for searching for cheap deals by user // idLanguages - list of languages to check for. if all are OK, pass in either an empty list or a list with exactly one entry which is an empty string private void checkArticle(string idProduct, System.Collections.Generic.List <string> idLanguages, string minCondition, string isFoil, string isSigned, string isAltered, string isPlayset, string matchingArticle, double maxAllowedPrice, double shippingAddition, double percentBelowOthers, bool checkTrend) { var sUrl = "https://api.cardmarket.com/ws/v2.0/articles/" + idProduct + "?minCondition=" + minCondition + "&start=0&maxResults=50"; if (isFoil != "") { sUrl += "&isFoil=" + isFoil; } if (isSigned != "") { sUrl += "&isSigned=" + isSigned; } if (isAltered != "") { sUrl += "&isAltered=" + isAltered; } if (isPlayset != "") { sUrl += "&isPlayset=" + isPlayset; } if (idLanguages.Count == 1 && idLanguages[0] != "") // if there is exactly one language specified, fetch only articles in that one language, otherwise get all { sUrl += "&idLanguage=" + idLanguages[0]; } XmlDocument doc2 = null; try { doc2 = MKMInteract.RequestHelper.makeRequest(sUrl, "GET"); } catch (Exception eError) { MKMHelpers.LogError("checking article id " + idProduct, eError.Message, false, sUrl); return; } var node2 = doc2.GetElementsByTagName("article"); var counter = 0; var noBestPrice = true; var aPrices = new float[4]; var bestPriceArticle = ""; float bestPriceInternational = 0; foreach (XmlNode offer in node2) { /* * Wantstates: * Empty = n/a * true = yes * false = no */ if (offer["seller"]["address"]["country"].InnerText != MKMHelpers.sMyOwnCountry && domesticCheck.Checked) { continue; } bool languageOk = true; if (idLanguages.Count > 1) // only some languages were specified, filter { languageOk = false; foreach (string lang in idLanguages) { if (lang == offer["language"]["idLanguage"].InnerText) { languageOk = true; break; } } } if (!languageOk) { continue; } // save cheapest price found anywhere aPrices[counter] = Convert.ToSingle(offer["price"].InnerText, CultureInfo.InvariantCulture); if (noBestPrice) { bestPriceInternational = aPrices[counter]; noBestPrice = false; } if (aPrices[0] + shippingAddition > maxAllowedPrice) { //frm1.logBox.Invoke(new Form1.logboxAppendCallback(frm1.logBoxAppend), "Price higher than Max Price\n"); continue; } if (counter == 0) { bestPriceArticle = offer["idArticle"].InnerText; // if looking for matching article, no point to continue if it is not the cheapest - perhaps could be modified to pick things that are among matching? if (matchingArticle != "" && matchingArticle != bestPriceArticle) { break; } } counter++; if (counter == 3) { double factor = percentBelowOthers; factor = factor / 100 + 1; MainView.Instance.LogMainWindow("Price 1: " + aPrices[0] + " Price 2: " + aPrices[1]); MainView.Instance.LogMainWindow( "Factor Price 1: " + Math.Round(aPrices[0] * factor + shippingAddition, 2) + " Factor Price 2: " + Math.Round(aPrices[1] * factor + shippingAddition, 2)); //X% under others if ( (aPrices[0] * factor + shippingAddition < aPrices[1]) && (aPrices[0] * factor + shippingAddition < aPrices[2]) ) { double fTrendprice = 100000; // fictive price if (checkTrend) { //check Trend Price try { var doc3 = MKMInteract.RequestHelper.makeRequest( "https://api.cardmarket.com/ws/v2.0/products/" + idProduct, "GET"); fTrendprice = Convert.ToDouble(doc3.GetElementsByTagName("TREND")[0].InnerText.Replace(".", ",")); MainView.Instance.LogMainWindow("Trend: " + fTrendprice); } catch (Exception eError) { MKMHelpers.LogError("checking trend price for " + offer["product"]["locName"].InnerText, eError.Message, false); } } //only relevant if we search domestic if (domesticCheck.Checked) { // is best price international (+/-5%)? if (!(aPrices[0] * 0.95 <= bestPriceInternational)) { break; } } // X% under TREND if (aPrices[0] * factor < fTrendprice) { MainView.Instance.LogMainWindow("Found cheap offer, ID " + bestPriceArticle); try { var sRequestXML = MKMInteract.RequestHelper.addCartBody(bestPriceArticle); sRequestXML = MKMInteract.RequestHelper.getRequestBody(sRequestXML); MKMInteract.RequestHelper.makeRequest("https://api.cardmarket.com/ws/v2.0/shoppingcart", "PUT", sRequestXML); } catch (Exception eError) { MKMHelpers.LogError("adding article " + offer["product"]["locName"].InnerText + " to cart", eError.Message, false); } } } break; } } }
/// <summary> /// For actually performing the check for cheap deals from a given user, expected to be run in its separate thread. /// </summary> /// <param name="selectedExpansionID">Leave as empty string if all expansion should be checked.</param> private void checkUserRun(string user, string isFoil, string isSigned, string isAltered, string isPlayset, string minCondition, bool domesticOnly, double maxPrice, double shippingAddition, double percentBelowOthers, bool checkTrend, System.Collections.Generic.List <string> selectedLanguage, string selectedExpansionID = "") { MainView.Instance.LogMainWindow("Check for cheap deals from seller '" + user + "'..."); if (domesticOnly) { MainView.Instance.LogMainWindow("WARNING - domestics only is checked, if the specified seller is from a foreign country, no deals will be found."); } // Go through the stock of a specified user, checks for cheap deals and add them to the cart. int start = 0; while (true) { String sUrl = "https://api.cardmarket.com/ws/v2.0/users/" + user + "/articles?start=" + start + "&maxResults=1000"; XmlDocument doc2 = null; try { // get the users stock, filtered by the selected parameters doc2 = MKMInteract.RequestHelper.makeRequest(sUrl, "GET"); } catch (Exception eError) { MKMHelpers.LogError("looking for cheap deals from user " + user, eError.Message, false, sUrl); break; } var node2 = doc2.GetElementsByTagName("article"); foreach (XmlNode article in node2) { if (selectedExpansionID != "") // if we want only cards from a specified set, check if this product is from that set using local database { DataRow card = MKMDbManager.Instance.GetSingleCard(article[MKMDbManager.InventoryFields.ProductID].InnerText); if (card == null || card.Field <string>(MKMDbManager.InventoryFields.ExpansionID) != selectedExpansionID) // compare { continue; } } if ( // do as much filtering here as possible to reduce the number of API calls MKMHelpers.IsBetterOrSameCondition(article["condition"].InnerText, minCondition) && (!foilBox.Checked || article["isFoil"].InnerText == "true") && (!playsetBox.Checked || article["isPlayset"].InnerText == "true") && (selectedLanguage[0] == "" || article["language"]["idLanguage"].InnerText == selectedLanguage[0]) && (!signedBox.Checked || article["isSigned"].InnerText == "true") && (!signedBox.Checked || article["isAltered"].InnerText == "true") && (maxPrice >= Convert.ToDouble(article["price"].InnerText, CultureInfo.InvariantCulture)) ) { MainView.Instance.LogMainWindow("Checking product ID: " + article["idProduct"].InnerText); checkArticle(article["idProduct"].InnerText, selectedLanguage, minCondition, isFoil, isSigned, isAltered, isPlayset, article["idArticle"].InnerText, maxPrice, shippingAddition, percentBelowOthers, checkTrend); } } if (node2.Count != 1000) // there is no additional items to fetch { break; } start += 1000; } MainView.Instance.LogMainWindow("Check finished."); }
/// <summary> /// Checks the expected ROI of a given display. Expected to be run in a separate thread /// </summary> private void checkDisplayPrice_run(float fMythicFactor, float fPackUncommon, float fPackRareMythic, float fRareCardsNotinPacks, float fMythicCardsNotinPacks, float fUncommonCardsNotinPacks, float fBoxContent, string editionID) { try { //used to determine index of best start edition //MessageBox.Show((editionBox.SelectedIndex.ToString())); var doc = MKMInteract.RequestHelper.getExpansionsSingles(editionID); //would be easier if mkm would deliver detailed info with this call but ... MainView.Instance.LogMainWindow("====== Expansion Stats ======"); //Multiplier float fCardsInSet = doc.SelectNodes("response/single").Count; MainView.Instance.LogMainWindow("Cards in set: " + fCardsInSet); var xRares = doc.SelectNodes("response/single/rarity[. = \"Rare\"]"); var xMythic = doc.SelectNodes("response/single/rarity[. = \"Mythic\"]"); var xUncommon = doc.SelectNodes("response/single/rarity[. = \"Uncommon\"]"); var iCountRares = xRares.Count - fRareCardsNotinPacks; //53F; var iCountMythics = xMythic.Count - fMythicCardsNotinPacks; //15F; var iCountUncommons = xUncommon.Count - fUncommonCardsNotinPacks; //80F; MainView.Instance.LogMainWindow("Rares in set: " + iCountRares); MainView.Instance.LogMainWindow("Mythic in set: " + iCountMythics); MainView.Instance.LogMainWindow("Uncommon in set: " + iCountUncommons); //factors per booster var fFactorUncommon = fPackUncommon / iCountUncommons; //0,0375 var fFactorMythicRareCombined = fPackRareMythic / (iCountRares + iCountMythics); // 0,014 var fFactorMythic = fFactorMythicRareCombined / fMythicFactor; //chance is 1:8 fpr Mythic var fFactorRare = fFactorMythicRareCombined / fMythicFactor * (fMythicFactor - 1); MainView.Instance.LogMainWindow("====== Calculated Booster Factors ======"); MainView.Instance.LogMainWindow("Uncommon: " + fFactorUncommon); MainView.Instance.LogMainWindow("MR Combo: " + fFactorMythicRareCombined); MainView.Instance.LogMainWindow("Rare:" + fFactorRare); MainView.Instance.LogMainWindow("Mythic:" + fFactorMythic); var fFactorUncommonBox = fFactorUncommon * fBoxContent; var fFactorMythicRareCombinedBox = fFactorMythicRareCombined * fBoxContent; var fFactorMythicBox = fFactorMythic * fBoxContent; var fFactorRareBox = fFactorRare * fBoxContent; MainView.Instance.LogMainWindow("====== Calculated Box Factors ======"); MainView.Instance.LogMainWindow("Uncommon: " + fFactorUncommonBox); MainView.Instance.LogMainWindow("MR Combo: " + fFactorMythicRareCombinedBox); MainView.Instance.LogMainWindow("Rare:" + fFactorRareBox); MainView.Instance.LogMainWindow("Mythic:" + fFactorMythicBox); xRares = doc.SelectNodes("response/single"); float fBoxValue = 0; foreach (XmlNode xn in xRares) { if (xn["rarity"].InnerText == "Rare") { MainView.Instance.LogMainWindow("Checking (R): " + xn["enName"].InnerText); var doc2 = MKMInteract.RequestHelper.makeRequest( "https://api.cardmarket.com/ws/v2.0/products/" + xn["idProduct"].InnerText, "GET"); if (doc2.HasChildNodes) { var fCardPrice = (float)Convert.ToDouble( doc2.SelectSingleNode("response/product/priceGuide/SELL").InnerText, CultureInfo.InvariantCulture); MainView.Instance.LogMainWindow("Current SELL Price: " + fCardPrice); fBoxValue += fCardPrice * fFactorMythicRareCombinedBox; //changed cause it's actually a rare + Mythic not rare or mythic I think? was fFactorRareBox; } } if (xn["rarity"].InnerText == "Mythic") { MainView.Instance.LogMainWindow("Checking (M): " + xn["enName"].InnerText); var doc2 = MKMInteract.RequestHelper.makeRequest( "https://api.cardmarket.com/ws/v2.0/products/" + xn["idProduct"].InnerText, "GET"); if (doc2.HasChildNodes) { var fCardPrice = (float)Convert.ToDouble( doc2.SelectSingleNode("response/product/priceGuide/SELL").InnerText, CultureInfo.InvariantCulture); MainView.Instance.LogMainWindow("Current SELL Price: " + fCardPrice); fBoxValue += fCardPrice * fFactorMythicBox; } } if (xn["rarity"].InnerText == "Uncommon") { MainView.Instance.LogMainWindow("Checking (U): " + xn["enName"].InnerText); var doc2 = MKMInteract.RequestHelper.makeRequest( "https://api.cardmarket.com/ws/v2.0/products/" + xn["idProduct"].InnerText, "GET"); if (doc2.HasChildNodes) { var fCardPrice = (float)Convert.ToDouble( doc2.SelectSingleNode("response/product/priceGuide/SELL").InnerText, CultureInfo.InvariantCulture); MainView.Instance.LogMainWindow("Current SELL Price: " + fCardPrice); fBoxValue += fCardPrice * fFactorUncommonBox; } } } MainView.Instance.LogMainWindow("Calculated Result *: " + fBoxValue); MainView.Instance.LogMainWindow( "* Estimated average booster box singles value at MKM SELL Pricing (EUR)\n"); } catch (Exception eError) { MKMHelpers.LogError("checking display prices", eError.Message, true); } }
/// <summary> /// For actually performing the check for cheap deals from wantlist, expected to be run in its separate thread /// </summary> private void checkListRun(string listID, double maxAllowedPrice, double shippingAdd, double percentBelow, bool checkTrend) { XmlDocument doc = null; try { doc = MKMInteract.RequestHelper.getWantsListByID(listID); } catch (Exception eError) { MKMHelpers.LogError("checking wantlist ID " + listID, eError.Message, true); return; } MainView.Instance.LogMainWindow("Starting to check your wantlist ID:" + listID + " ..."); var node = doc.GetElementsByTagName("item"); foreach (XmlNode article in node) { // articles in a wantlist can be either product or metaproducts (the same card from multiple sets) // each metaproduct needs to be expanded into a list of products that are "realized" by it System.Collections.Generic.List <XmlNode> products = new System.Collections.Generic.List <XmlNode>(); if (article["type"].InnerText == "product") { products.Add(article["product"]); } else // it is a metaproduct { try { XmlDocument metaDoc = MKMInteract.RequestHelper.getMetaproduct(article["metaproduct"]["idMetaproduct"].InnerText); XmlNodeList mProducts = metaDoc.GetElementsByTagName("product"); foreach (XmlNode prod in mProducts) { products.Add(prod); } } catch (Exception eError) { MKMHelpers.LogError("checking wantlist metaproduct ID " + article["metaproduct"]["idMetaproduct"], eError.Message, false); continue; } } foreach (XmlNode product in products) { // As of 25.6.2019, the countArticles and countFoils fields are not described in MKM documentation, but they seem to be there. // I think this is indeed a new thing that appeared since MKM started to force promo-sets as foils only // We can use this to prune lot of useless calls that will end up in empty responses from searching for non foils in promo (foil only) sets int total = int.Parse(product["countArticles"].InnerText); int foils = int.Parse(product["countFoils"].InnerText); if ((article["isFoil"].InnerText == "true" && foils == 0) || // there are only non-foils of this article and we want foils (article["isFoil"].InnerText == "false" && foils == total)) // there are only foils and we want non-foil { continue; } MainView.Instance.LogMainWindow("checking:" + product["enName"].InnerText + " from " + product["expansionName"].InnerText + "..."); // a wantlist item can have more idLanguage entries, one for each wanted language System.Collections.Generic.List <string> selectedLanguages = new System.Collections.Generic.List <string>(); foreach (XmlNode langNodes in product.ChildNodes) { if (langNodes.Name == "idLanguage") { selectedLanguages.Add(langNodes.InnerText); } } checkArticle(product["idProduct"].InnerText, selectedLanguages, article["minCondition"].InnerText, article["isFoil"].InnerText, article["isSigned"].InnerText, article["isAltered"].InnerText, // isPlayset seems to no longer be part of the API, instead there is a count of how many times is the card wanted, let's use it int.Parse(article["count"].InnerText) == 4 ? "true" : "false", "", maxAllowedPrice, shippingAdd, percentBelow, checkTrend); } } MainView.Instance.LogMainWindow("Check finished."); }
// reload data each time the form is made visible in case the user's stock has changed so they can reload the stockview this way private void StockView_VisibleChanged(object sender, EventArgs e) { if (Visible) { int start = 1; var articles = new DataTable(); try { while (true) { var doc = MKMInteract.RequestHelper.readStock(start); if (doc.HasChildNodes) { var result = doc.GetElementsByTagName("article"); int elementCount = 0; foreach (XmlNode article in result) { if (article["condition"] != null) // is null for articles that are not cards (boosters etc.) - don't process those { MKMMetaCard m = new MKMMetaCard(article); m.WriteItselfIntoTable(articles, true, MCFormat.MKM); elementCount++; } } if (elementCount != 100) { break; } start += elementCount; } else { break; // document is empty -> end*/ } } // Remove columns we don't want showing // TODO - what is and isn't shown should probably be customizable and left to the user to choose in some way articles.Columns.Remove(MCAttribute.ArticleID); articles.Columns.Remove(MCAttribute.ProductID); articles.Columns.Remove(MCAttribute.LanguageID); articles.Columns.Remove(MCAttribute.CardNumber); var dj = MKMDbManager.JoinDataTables(articles, MKMDbManager.Instance.Expansions, (row1, row2) => row1.Field <string>(MKMDbManager.InventoryFields.ExpansionID) == row2.Field <string>(MKMDbManager.ExpansionsFields.ExpansionID)); dj.Columns.Remove(MCAttribute.ExpansionID); // duplicated dj.Columns.Remove(MKMDbManager.ExpansionsFields.ExpansionID); // ...and we don't want it anyway dj.Columns.Remove(MKMDbManager.ExpansionsFields.Name); // duplicated dj.Columns[dj.Columns.IndexOf(MCAttribute.Name)].SetOrdinal(0); dj.Columns[dj.Columns.IndexOf(MCAttribute.Expansion)].SetOrdinal(1); dj.Columns[dj.Columns.IndexOf(MCAttribute.Language)].SetOrdinal(2); stockGridView.DataSource = dj; buttonExport.Enabled = true; } catch (Exception eError) { MKMHelpers.LogError("listing stock in Stock View", eError.Message, true); } } }
private void checkEditionButton_Click(object sender, EventArgs e) { checkEditionButton.Enabled = false; checkEditionButton.Text = "Checking cheap deals..."; var isFoil = ""; var isSigned = ""; var isAltered = ""; var isPlayset = ""; var minCondition = conditionCombo.Text; if (foilBox.Checked) { isFoil = "true"; } if (signedBox.Checked) { isSigned = "true"; } if (alteredBox.Checked) { isAltered = "true"; } if (playsetBox.Checked) { isPlayset = "true"; } if (checkBoxUser.Enabled) { if (domnesticCheck.Checked) { frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "WARNING - domestics only is checked, if the specified seller is from a foreign country, no deals will be found.\n"); } // Go through the stock of a specified user, checks for cheap deals and add them to the cart. int start = 0; double maxAllowedPrice = Convert.ToDouble(maxPrice.Text); while (true) { String sUrl = "https://api.cardmarket.com/ws/v2.0/users/" + textBoxUser.Text + "/articles?start=" + start + "&maxResults=1000"; try { // get the users stock, filtered by the selected parameters var doc2 = MKMInteract.RequestHelper.makeRequest(sUrl, "GET"); var node2 = doc2.GetElementsByTagName("article"); String language = (langCombo.SelectedItem as MKMHelpers.ComboboxItem).Value.ToString(); foreach (XmlNode article in node2) { if ( // do as much filtering here as possible to reduce the number of API calls MKMHelpers.IsBetterOrSameCondition(article["condition"].InnerText, conditionCombo.Text) && (!foilBox.Checked || article["isFoil"].InnerText == "true") && (!playsetBox.Checked || article["isPlayset"].InnerText == "true") && (language == "" || article["language"]["idLanguage"].InnerText == language) && (!signedBox.Checked || article["isSigned"].InnerText == "true") && (!signedBox.Checked || article["isAltered"].InnerText == "true") && (maxAllowedPrice >= Convert.ToDouble(article["price"].InnerText, CultureInfo.InvariantCulture)) ) { frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "Checking: " + article["idProduct"].InnerText + "\n"); checkArticle(article["idProduct"].InnerText, language, minCondition, isFoil, isSigned, isAltered, isPlayset, article["idArticle"].InnerText); frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "Done.\n"); } } if (node2.Count != 1000) // there is no additional items to fetch { break; } } catch (Exception eError) { frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "ERR Msg : " + eError.Message + "\n"); frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "ERR URL : " + sUrl + "\n"); using (var sw = File.AppendText(@".\\error_log.txt")) { sw.WriteLine("ERR Msg : " + eError.Message); sw.WriteLine("ERR URL : " + sUrl); } } start += 1000; } } else { var sEdId = editionBox.SelectedItem.ToString(); var sT = dt.Clone(); var result = dt.Select(string.Format("[Expansion ID] = '{0}'", sEdId)); foreach (var row in result) { sT.ImportRow(row); } foreach (DataRow oRecord in sT.Rows) { frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "Checking: " + oRecord["idProduct"] + "\n"); checkArticle(oRecord["idProduct"].ToString(), (langCombo.SelectedItem as MKMHelpers.ComboboxItem).Value.ToString(), minCondition, isFoil, isSigned, isAltered, isPlayset, ""); frm1.logBox.Invoke(new MainView.logboxAppendCallback(frm1.logBoxAppend), "Done.\n"); } } checkEditionButton.Text = "Check now"; checkEditionButton.Enabled = true; }