From 2c387b1fdf3367e2561c5bd017da2c4df6c3d8e7 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 20 Dec 2018 20:16:40 -0500 Subject: [PATCH] Issue2: Added a walkthrough for creating a config file using a battlenet URL instead of manually extracting each parameter. --- Sc2MmrReader/Application.cs | 274 ++++++++++++++++++ .../Config.json.example | 2 - Sc2MmrReader/ConfigFileManager.cs | 84 ++++++ Sc2MmrReader/Program.cs | 143 +-------- Sc2MmrReader/ReaderConfig.cs | 17 ++ Sc2MmrReader/Sc2MmrReader.csproj | 8 +- 6 files changed, 379 insertions(+), 149 deletions(-) create mode 100644 Sc2MmrReader/Application.cs rename Config.json.example => Sc2MmrReader/Config.json.example (89%) create mode 100644 Sc2MmrReader/ConfigFileManager.cs diff --git a/Sc2MmrReader/Application.cs b/Sc2MmrReader/Application.cs new file mode 100644 index 0000000..0ac7371 --- /dev/null +++ b/Sc2MmrReader/Application.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Sc2MmrReader { + /// + /// This is the main application. + /// + public class Application { + private String _exePath; + private String[] _programArgs; + + /// + /// Creates a new application with the given exe information. + /// + /// The path to the executable. + /// The program arguments that were passed in. + public Application(String exePath, String[] args) { + _exePath = exePath; + _programArgs = args; + } + + /// + /// Runs the application. Does not return until the application should close. + /// + public void Run() { + String[] args = _programArgs; + String exeParent = System.IO.Directory.GetParent(_exePath).FullName; + String configFilePath = Path.Combine(new string[] { exeParent, "Config.json" }); + + if (args.Length > 1) { + configFilePath = args[1]; + } + + ReaderConfig config = RunConfigFileFlow(exeParent, configFilePath); + if (config == null) { + return; // We're done. + } + + // Read MMR + MmrReader reader = new MmrReader(config); + reader.Run(); + } + + /// + /// Reads the config file or walks the user through creating or upgrading one. + /// + /// The directory the executable is in. + /// The path to the config file we should use or create. + /// Returns the config file to use or null if the application should exit. + private ReaderConfig RunConfigFileFlow(String exeParent, String configFilePath) { + // First make sure the file exists + if (!File.Exists(configFilePath)) { + RunCreateConfigFileFlow(configFilePath); + } + + ReaderConfig config = ConfigFileManager.ReadConfigFile(configFilePath); + if (config == null) { + Console.WriteLine("There was a problem reading the config file."); + Console.WriteLine("Press any key to exit."); + Console.ReadKey(); + return null; + } + + // Check the version of the config file + if (config.Version < ReaderConfig.ReaderConfigVersion) { + Console.WriteLine("The specified config file is an older version. See Config.json.example for the current format."); + Console.WriteLine("Would you like Sc2MmrReader to attempt to upgrade to the new format? New parameters will get default values."); + + String response = GetInputFromUser(new String[] { "yes", "no" }); + + // Just quit if they said no. They can set the config settings themselves. + if (response == "no") { + return null; + } + + // Otherwise update the config file + config.Version = ReaderConfig.ReaderConfigVersion; + if (ConfigFileManager.SaveConfigFile(config, configFilePath)) { + Console.WriteLine("The config file has been updated. Please check that the settings are correct and then restart Sc2MmrReader. The path is:"); + Console.WriteLine("\t" + configFilePath); + Console.WriteLine("Press any key to exit."); + Console.ReadKey(); + return null; + } else { + Console.WriteLine("There was a problem saving the config file. Please update it manually."); + Console.WriteLine("Press any key to exit."); + Console.ReadKey(); + return null; + } + + } else if (config.Version > ReaderConfig.ReaderConfigVersion) { + // If it's too new, tell them to update + Console.WriteLine("The specified config file version is for a new version of Sc2MmrReader. Please update to the latest version or downgrade your config file to version " + ReaderConfig.ReaderConfigVersion); + Console.WriteLine("Press any key to exit."); + Console.ReadKey(); + return null; + } + + // Make the paths absolute with respect to the root of the EXE file if they are relative + if (!Path.IsPathRooted(config.MmrFilePath)) { + config.MmrFilePath = Path.Combine(new string[] { exeParent, config.MmrFilePath }); + } + + if (!Path.IsPathRooted(config.DataDirectory)) { + config.DataDirectory = Path.Combine(new string[] { exeParent, config.DataDirectory }); + } + + Console.WriteLine("Using the following config file: " + configFilePath); + Console.WriteLine("Outputting MMR to: " + config.MmrFilePath); + Console.WriteLine(); + + return config; + } + + /// + /// Walks the user through creating a new config file. If the user follows the flow, the file will be created by the end. + /// If they say no or cancel partway through there will not be a file afterwards. + /// + /// The path they specified to create it at. + private void RunCreateConfigFileFlow(String configFilePath) { + Console.WriteLine("You do not appear to have a config file at: "); + Console.WriteLine("\t" + configFilePath); + Console.WriteLine(); + Console.WriteLine("Would you like to create one? "); + String response = GetInputFromUser(new String[]{ "yes", "no" }); + Console.WriteLine(); + if (response == "yes") { + // Load in the default values from the default file: + ReaderConfig config = ReaderConfig.CreateDefault(); + Console.WriteLine(); + Console.WriteLine("First we'll need the information for the ladder you want to get the MMR for."); + Console.WriteLine("Please enter the URL to the ladder you would like to monitor."); + Console.WriteLine("You can find this by doing the following:"); + Console.WriteLine(" 1) Navigate to Starcraft2.com"); + Console.WriteLine(" 2) Log in to your Blizzard account"); + Console.WriteLine(" 3) Navigate to View Profile-->Ladders-->(Pick a Ladder). Note: The ladders are listed under \"CURRENT SEASON LEAGUES\""); + Console.WriteLine(" 4) Copy the URL of the page and paste it below."); + Console.WriteLine(" Example: https://starcraft2.com/en-us/profile/1/1/1986271/ladders?ladderId=274006"); + Console.WriteLine(); + Console.Write(" (enter a URL): "); + response = Console.ReadLine(); + while (response.ToLower() != "q" && !ParseAccountUrlIntoConfig(response, config)) { + Console.WriteLine("That URL is not valid. Make sure you get the URL of a specific ladder and not your profile, or enter q to quit."); + Console.Write(" (enter a URL): "); + response = Console.ReadLine(); + } + Console.WriteLine(); + + // If they asked to quit, just stop. + if (response.ToLower() == "q") { + Console.WriteLine("Exiting."); + return; + } + + // Otherwise, the URL parsed sucessfully. + Console.WriteLine("Great! That URL seems valid."); + Console.WriteLine(); + response = ""; + while (String.IsNullOrEmpty(response)) { + Console.WriteLine("Please enter your Client ID (See README.md if you dont know what that is)."); + Console.Write(" (ClientId): "); + response = Console.ReadLine(); + } + Console.WriteLine(); + + // Set their response on the config. + config.ClientId = response; + + Console.WriteLine(); + response = ""; + while (String.IsNullOrEmpty(response)) { + Console.WriteLine("Please enter your Client Secret (See README.md if you dont know what that is)."); + Console.Write(" (ClientSecret): "); + response = Console.ReadLine(); + } + Console.WriteLine(); + + // Set their secret on the config + config.ClientSecret = response; + + if (!ConfigFileManager.SaveConfigFile(config, configFilePath)) { + Console.WriteLine("Uh oh, we were not able to save the config to: "); + Console.WriteLine("\t" + configFilePath); + Console.WriteLine("Please check that is a valid path and Sc2MmrReader has access to write there and try again!"); + return; + } + + // Print a success message and continue on to the rest of the app with the info that was written. + Console.WriteLine("All set! You can check the following path to verify your settings are correct at any time:"); + Console.WriteLine("\t" + configFilePath); + Console.WriteLine(); + } else { + // Just return + } + } + + /// + /// Parses the ladder and profile information out of the given account URL into the given config. + /// The given config is only modified if the URL parses correctly. + /// + /// A Blizzard ladder URL of the form: https://starcraft2.com/[Locale]/profile/[RegionId]/[RealmId]/[ProfileId]/ladders?ladderId=[LadderId] + /// The config to populate + /// Returns true if the URL was the right format and was successfully parsed. False otherwise. + private bool ParseAccountUrlIntoConfig(String url, ReaderConfig config) { + String[] regionIdMap = new String[] { "", "US", "EU", "KO", "", "CN" }; + const String ladderIdRegex = "\\/profile\\/([0-9]{1})\\/([0-9]{1})\\/([0-9]*)\\/ladders\\?ladderId=([0-9]*)"; + Match match = Regex.Match(url, ladderIdRegex, RegexOptions.None, new TimeSpan(0, 0, 5)); + if (match.Success) { + // There should be 5 groups (4 + the full match at [0]) + if (match.Groups.Count != 5) + return false; + + long regionId = -1; + if (!long.TryParse(match.Groups[1].Value, out regionId)) + return false; + if (regionId >= regionIdMap.Length) { + Console.WriteLine("Unknown RegionId in the URL: " + regionId); + return false; + } + + int realmId = -1; + if (!int.TryParse(match.Groups[2].Value, out realmId)) + return false; + + long profileId = -1; + if (!long.TryParse(match.Groups[3].Value, out profileId)) + return false; + + long ladderId = -1; + if (!long.TryParse(match.Groups[4].Value, out ladderId)) + return false; + + // Fill out the config + config.RegionId = regionIdMap[regionId]; + config.RealmId = realmId; + config.ProfileId = profileId; + config.LadderId = ladderId; + + return true; + } else { + // No matches found - the URL is probably not formatted correctly + return false; + } + } + + /// + /// Asks the user to enter one of the given options and returns what they picked. + /// + /// An array of options for the user to pick from. + /// Returns one of the options in the given array. + private String GetInputFromUser(String[] options) { + String optionsLine = " ("; + int index = 0; + foreach (String option in options) { + optionsLine += options[index] + "/"; + index++; + } + optionsLine = optionsLine.Substring(0, optionsLine.Length - 1) + "): "; + + String input = ""; + while (!options.Contains(input)) { + Console.Write(optionsLine); + input = Console.ReadLine(); + } + + return input; + } + } +} diff --git a/Config.json.example b/Sc2MmrReader/Config.json.example similarity index 89% rename from Config.json.example rename to Sc2MmrReader/Config.json.example index 764a69e..e9fca38 100644 --- a/Config.json.example +++ b/Sc2MmrReader/Config.json.example @@ -1,10 +1,8 @@ { - "Version": 1, "MsPerRead": 5000, "DataDirectory": "", "MmrFilePath": "mmr.txt", "RegionId": "US", - "RealmId": 1, "ProfileId": 1986271, "LadderId": 274006, "ClientId": "GetThisFromTheBlizzardDeveloperApiSite", diff --git a/Sc2MmrReader/ConfigFileManager.cs b/Sc2MmrReader/ConfigFileManager.cs new file mode 100644 index 0000000..21212e6 --- /dev/null +++ b/Sc2MmrReader/ConfigFileManager.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.Web.Script.Serialization; + +namespace Sc2MmrReader { + /// + /// Provides methods for reading and writing the config file. + /// + public class ConfigFileManager { + /// + /// Reads the configuration file at the given path. + /// + /// A path to a JSON config file to read. The JSON file must exactly match the ReaderConfig class. + /// Returns the configuration read in the file or null if there was an error. The error is printed to stdout as well. + public static ReaderConfig ReadConfigFile(String path) { + String configFileText = null; + try { + configFileText = File.ReadAllText(path); + } catch (IOException ex) { + Console.WriteLine("Unable to open the config file at " + path); + Console.WriteLine("Does the file exist?"); + Console.WriteLine("Exception: " + ex.Message); + return null; + } + + JavaScriptSerializer deserializer = new JavaScriptSerializer(); + ReaderConfig configFile = null; + + try { + configFile = deserializer.Deserialize(configFileText); + } catch (Exception ex) { + Console.WriteLine("Could not deserialize the config file. Is the format correct? See Config.json.example for correct usage."); + Console.WriteLine("Exception: " + ex.Message); + if (ex.InnerException != null) { + Console.WriteLine("Inner Exception: " + ex.InnerException.Message); + } + } + + return configFile; + } + + /// + /// Saves the given config file to the given path. + /// + /// The config to write. + /// The path to save the config file to + /// Returns true if it succeeded, false if there was an error. + public static bool SaveConfigFile(ReaderConfig config, String path) { + JavaScriptSerializer serializer = new JavaScriptSerializer(); + String configString = null; + try { + configString = serializer.Serialize(config); + } catch (Exception ex) { + Console.WriteLine("Could not serialize the given config."); + Console.WriteLine("Exception: " + ex.Message); + if (ex.InnerException != null) { + Console.WriteLine("Inner Exception: " + ex.InnerException.Message); + } + + return false; + } + + // Do a poor mans prettify since JavaScriptSerializer doesn't support + // formatting the output. This could be improved by using an actual library like JSON.net + configString = configString.Insert(configString.IndexOf("{") + 1, Environment.NewLine + "\t"); + configString = configString.Insert(configString.LastIndexOf("}"), Environment.NewLine); + configString = configString.Replace("\":", "\": "); + configString = configString.Replace(",\"", "," + Environment.NewLine + "\t\""); + + // Write it to a file + try { + File.WriteAllText(path, configString); + } catch (Exception ex) { + Console.WriteLine("Could not write the config file to " + path); + Console.WriteLine("Exception: " + ex.Message); + if (ex.InnerException != null) { + Console.WriteLine("Inner Exception: " + ex.InnerException.Message); + } + } + + return true; + } + } +} diff --git a/Sc2MmrReader/Program.cs b/Sc2MmrReader/Program.cs index 887fdc2..a3f2a25 100644 --- a/Sc2MmrReader/Program.cs +++ b/Sc2MmrReader/Program.cs @@ -4,153 +4,14 @@ namespace Sc2MmrReader { class Program { - /// - /// Reads the configuration file at the given path. - /// - /// A path to a JSON config file to read. The JSON file must exactly match the ReaderConfig class. - /// Returns the configuration read in the file or null if there was an error. The error is printed to stdout as well. - private static ReaderConfig ReadConfigFile(String path) { - String configFileText = null; - try { - configFileText = File.ReadAllText(path); - } catch (IOException ex) { - Console.WriteLine("Unable to open the config file at " + path); - Console.WriteLine("Does the file exist?"); - Console.WriteLine("Exception: " + ex.Message); - return null; - } - - JavaScriptSerializer deserializer = new JavaScriptSerializer(); - ReaderConfig configFile = null; - - try { - configFile = deserializer.Deserialize(configFileText); - } catch (Exception ex) { - Console.WriteLine("Could not deserialize the config file. Is the format correct? See Config.json.example for correct usage."); - Console.WriteLine("Exception: " + ex.Message); - if (ex.InnerException != null) { - Console.WriteLine("Inner Exception: " + ex.InnerException.Message); - } - } - - return configFile; - } - - /// - /// Saves the given config file to the given path. - /// - /// The config to write. - /// The path to save the config file to - /// Returns true if it succeeded, false if there was an error. - private static bool SaveConfigFile(ReaderConfig config, String path) { - JavaScriptSerializer serializer = new JavaScriptSerializer(); - String configString = null; - try { - configString = serializer.Serialize(config); - } catch (Exception ex) { - Console.WriteLine("Could not serialize the given config."); - Console.WriteLine("Exception: " + ex.Message); - if (ex.InnerException != null) { - Console.WriteLine("Inner Exception: " + ex.InnerException.Message); - } - - return false; - } - - // Do a poor mans prettify since JavaScriptSerializer doesn't support - // formatting the output. This could be improved by using an actual library like JSON.net - configString = configString.Insert(configString.IndexOf("{") + 1, Environment.NewLine + "\t"); - configString = configString.Insert(configString.LastIndexOf("}"), Environment.NewLine); - configString = configString.Replace("\":", "\": "); - configString = configString.Replace(",\"", "," + Environment.NewLine + "\t\""); - - // Write it to a file - try { - File.WriteAllText(path, configString); - } catch (Exception ex) { - Console.WriteLine("Could not write the config file to " + path); - Console.WriteLine("Exception: " + ex.Message); - if (ex.InnerException != null) { - Console.WriteLine("Inner Exception: " + ex.InnerException.Message); - } - } - - return true; - } - /// /// Application entry point. Reads the config file and starts the MmrReader. /// /// Application arguments. Accepts an optional path to the config file. "Config.json" is used if none is specified. public static void Main(string[] args) { String exePath = System.Reflection.Assembly.GetEntryAssembly().Location; - String exeParent = System.IO.Directory.GetParent(exePath).FullName; - String configFilePath = Path.Combine(new string[] { exeParent, "Config.json" }); - - if (args.Length > 1) { - configFilePath = args[1]; - } else { - Console.WriteLine("No config file specified. Using default path: " + configFilePath); - } - - ReaderConfig config = ReadConfigFile(configFilePath); - if (config == null) { - Console.WriteLine("There was a problem reading the config file."); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - return; - } - - // Check the version of the config file - if (config.Version < ReaderConfig.ReaderConfigVersion) { - Console.WriteLine("The specified config file is an older version. See Config.json.example for the current format."); - Console.WriteLine("Would you like Sc2MmrReader to attempt to upgrade to the new format? New parameters will get default values."); - - String response = ""; - while (response != "yes" && response != "no") { - Console.WriteLine("Enter yes or no:"); - response = Console.ReadLine(); - } - - // Just quit if they said no. They can set the config settings themselves. - if (response == "no") { - return; - } - - // Otherwise update the config file - config.Version = ReaderConfig.ReaderConfigVersion; - if (SaveConfigFile(config, configFilePath)) { - Console.WriteLine("The config file has been updated. Please check that the settings are correct and then restart Sc2MmrReader. The path is:"); - Console.WriteLine("\t" + configFilePath); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - return; - } else { - Console.WriteLine("There was a problem saving the config file. Please update it manually."); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - return; - } - - } else if (config.Version > ReaderConfig.ReaderConfigVersion) { - // If it's too new, tell them to update - Console.WriteLine("The specified config file version is for a new version of Sc2MmrReader. Please update to the latest version or downgrade your config file to version " + ReaderConfig.ReaderConfigVersion); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - return; - } - - // Make the paths absolute with respect to the root of the EXE file if they are relative - if (!Path.IsPathRooted(config.MmrFilePath)) { - config.MmrFilePath = Path.Combine(new string[] { exeParent, config.MmrFilePath }); - } - - if (!Path.IsPathRooted(config.DataDirectory)) { - config.DataDirectory = Path.Combine(new string[] { exeParent, config.DataDirectory }); - } - - MmrReader reader = new MmrReader(config); - reader.Run(); + Application app = new Application(exePath, args); + app.Run(); } } } diff --git a/Sc2MmrReader/ReaderConfig.cs b/Sc2MmrReader/ReaderConfig.cs index 7fc8d0a..1b8aa52 100644 --- a/Sc2MmrReader/ReaderConfig.cs +++ b/Sc2MmrReader/ReaderConfig.cs @@ -26,6 +26,23 @@ public class ReaderConfig { // Information about who is connecting: public String ClientId { get; set; } // Get this by making a developer account for the Blizzard API. public String ClientSecret { get; set; } // Same as above. + + /// + /// Creates a ReaderConfig with default settings. + /// + /// Returns the created config. + public static ReaderConfig CreateDefault() { + // Fill out the default settings and version + ReaderConfig config = new ReaderConfig(); + config.Version = ReaderConfigVersion; + config.MsPerRead = 5000; + config.DataDirectory = ""; + config.MmrFilePath = "mmr.txt"; + + // The profile information and client info are left blank + // because they must be filled out by the user. + return config; + } } // Keep track of old config versions in case we want to be diff --git a/Sc2MmrReader/Sc2MmrReader.csproj b/Sc2MmrReader/Sc2MmrReader.csproj index ce75ae3..0217ab0 100644 --- a/Sc2MmrReader/Sc2MmrReader.csproj +++ b/Sc2MmrReader/Sc2MmrReader.csproj @@ -45,6 +45,8 @@ + + @@ -54,12 +56,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - \ No newline at end of file