Skip to content

Commit 2c387b1

Browse files
committed
Issue2: Added a walkthrough for creating a config file using a battlenet URL instead of manually extracting each parameter.
1 parent baa4ea2 commit 2c387b1

File tree

6 files changed

+379
-149
lines changed

6 files changed

+379
-149
lines changed

Sc2MmrReader/Application.cs

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Text.RegularExpressions;
7+
using System.Threading.Tasks;
8+
9+
namespace Sc2MmrReader {
10+
/// <summary>
11+
/// This is the main application.
12+
/// </summary>
13+
public class Application {
14+
private String _exePath;
15+
private String[] _programArgs;
16+
17+
/// <summary>
18+
/// Creates a new application with the given exe information.
19+
/// </summary>
20+
/// <param name="exePath">The path to the executable.</param>
21+
/// <param name="args">The program arguments that were passed in.</param>
22+
public Application(String exePath, String[] args) {
23+
_exePath = exePath;
24+
_programArgs = args;
25+
}
26+
27+
/// <summary>
28+
/// Runs the application. Does not return until the application should close.
29+
/// </summary>
30+
public void Run() {
31+
String[] args = _programArgs;
32+
String exeParent = System.IO.Directory.GetParent(_exePath).FullName;
33+
String configFilePath = Path.Combine(new string[] { exeParent, "Config.json" });
34+
35+
if (args.Length > 1) {
36+
configFilePath = args[1];
37+
}
38+
39+
ReaderConfig config = RunConfigFileFlow(exeParent, configFilePath);
40+
if (config == null) {
41+
return; // We're done.
42+
}
43+
44+
// Read MMR
45+
MmrReader reader = new MmrReader(config);
46+
reader.Run();
47+
}
48+
49+
/// <summary>
50+
/// Reads the config file or walks the user through creating or upgrading one.
51+
/// </summary>
52+
/// <param name="exeParent">The directory the executable is in.</param>
53+
/// <param name="configFilePath">The path to the config file we should use or create.</param>
54+
/// <returns>Returns the config file to use or null if the application should exit.</returns>
55+
private ReaderConfig RunConfigFileFlow(String exeParent, String configFilePath) {
56+
// First make sure the file exists
57+
if (!File.Exists(configFilePath)) {
58+
RunCreateConfigFileFlow(configFilePath);
59+
}
60+
61+
ReaderConfig config = ConfigFileManager.ReadConfigFile(configFilePath);
62+
if (config == null) {
63+
Console.WriteLine("There was a problem reading the config file.");
64+
Console.WriteLine("Press any key to exit.");
65+
Console.ReadKey();
66+
return null;
67+
}
68+
69+
// Check the version of the config file
70+
if (config.Version < ReaderConfig.ReaderConfigVersion) {
71+
Console.WriteLine("The specified config file is an older version. See Config.json.example for the current format.");
72+
Console.WriteLine("Would you like Sc2MmrReader to attempt to upgrade to the new format? New parameters will get default values.");
73+
74+
String response = GetInputFromUser(new String[] { "yes", "no" });
75+
76+
// Just quit if they said no. They can set the config settings themselves.
77+
if (response == "no") {
78+
return null;
79+
}
80+
81+
// Otherwise update the config file
82+
config.Version = ReaderConfig.ReaderConfigVersion;
83+
if (ConfigFileManager.SaveConfigFile(config, configFilePath)) {
84+
Console.WriteLine("The config file has been updated. Please check that the settings are correct and then restart Sc2MmrReader. The path is:");
85+
Console.WriteLine("\t" + configFilePath);
86+
Console.WriteLine("Press any key to exit.");
87+
Console.ReadKey();
88+
return null;
89+
} else {
90+
Console.WriteLine("There was a problem saving the config file. Please update it manually.");
91+
Console.WriteLine("Press any key to exit.");
92+
Console.ReadKey();
93+
return null;
94+
}
95+
96+
} else if (config.Version > ReaderConfig.ReaderConfigVersion) {
97+
// If it's too new, tell them to update
98+
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);
99+
Console.WriteLine("Press any key to exit.");
100+
Console.ReadKey();
101+
return null;
102+
}
103+
104+
// Make the paths absolute with respect to the root of the EXE file if they are relative
105+
if (!Path.IsPathRooted(config.MmrFilePath)) {
106+
config.MmrFilePath = Path.Combine(new string[] { exeParent, config.MmrFilePath });
107+
}
108+
109+
if (!Path.IsPathRooted(config.DataDirectory)) {
110+
config.DataDirectory = Path.Combine(new string[] { exeParent, config.DataDirectory });
111+
}
112+
113+
Console.WriteLine("Using the following config file: " + configFilePath);
114+
Console.WriteLine("Outputting MMR to: " + config.MmrFilePath);
115+
Console.WriteLine();
116+
117+
return config;
118+
}
119+
120+
/// <summary>
121+
/// Walks the user through creating a new config file. If the user follows the flow, the file will be created by the end.
122+
/// If they say no or cancel partway through there will not be a file afterwards.
123+
/// </summary>
124+
/// <param name="configFilePath">The path they specified to create it at.</param>
125+
private void RunCreateConfigFileFlow(String configFilePath) {
126+
Console.WriteLine("You do not appear to have a config file at: ");
127+
Console.WriteLine("\t" + configFilePath);
128+
Console.WriteLine();
129+
Console.WriteLine("Would you like to create one? ");
130+
String response = GetInputFromUser(new String[]{ "yes", "no" });
131+
Console.WriteLine();
132+
if (response == "yes") {
133+
// Load in the default values from the default file:
134+
ReaderConfig config = ReaderConfig.CreateDefault();
135+
Console.WriteLine();
136+
Console.WriteLine("First we'll need the information for the ladder you want to get the MMR for.");
137+
Console.WriteLine("Please enter the URL to the ladder you would like to monitor.");
138+
Console.WriteLine("You can find this by doing the following:");
139+
Console.WriteLine(" 1) Navigate to Starcraft2.com");
140+
Console.WriteLine(" 2) Log in to your Blizzard account");
141+
Console.WriteLine(" 3) Navigate to View Profile-->Ladders-->(Pick a Ladder). Note: The ladders are listed under \"CURRENT SEASON LEAGUES\"");
142+
Console.WriteLine(" 4) Copy the URL of the page and paste it below.");
143+
Console.WriteLine(" Example: https://starcraft2.com/en-us/profile/1/1/1986271/ladders?ladderId=274006");
144+
Console.WriteLine();
145+
Console.Write(" (enter a URL): ");
146+
response = Console.ReadLine();
147+
while (response.ToLower() != "q" && !ParseAccountUrlIntoConfig(response, config)) {
148+
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.");
149+
Console.Write(" (enter a URL): ");
150+
response = Console.ReadLine();
151+
}
152+
Console.WriteLine();
153+
154+
// If they asked to quit, just stop.
155+
if (response.ToLower() == "q") {
156+
Console.WriteLine("Exiting.");
157+
return;
158+
}
159+
160+
// Otherwise, the URL parsed sucessfully.
161+
Console.WriteLine("Great! That URL seems valid.");
162+
Console.WriteLine();
163+
response = "";
164+
while (String.IsNullOrEmpty(response)) {
165+
Console.WriteLine("Please enter your Client ID (See README.md if you dont know what that is).");
166+
Console.Write(" (ClientId): ");
167+
response = Console.ReadLine();
168+
}
169+
Console.WriteLine();
170+
171+
// Set their response on the config.
172+
config.ClientId = response;
173+
174+
Console.WriteLine();
175+
response = "";
176+
while (String.IsNullOrEmpty(response)) {
177+
Console.WriteLine("Please enter your Client Secret (See README.md if you dont know what that is).");
178+
Console.Write(" (ClientSecret): ");
179+
response = Console.ReadLine();
180+
}
181+
Console.WriteLine();
182+
183+
// Set their secret on the config
184+
config.ClientSecret = response;
185+
186+
if (!ConfigFileManager.SaveConfigFile(config, configFilePath)) {
187+
Console.WriteLine("Uh oh, we were not able to save the config to: ");
188+
Console.WriteLine("\t" + configFilePath);
189+
Console.WriteLine("Please check that is a valid path and Sc2MmrReader has access to write there and try again!");
190+
return;
191+
}
192+
193+
// Print a success message and continue on to the rest of the app with the info that was written.
194+
Console.WriteLine("All set! You can check the following path to verify your settings are correct at any time:");
195+
Console.WriteLine("\t" + configFilePath);
196+
Console.WriteLine();
197+
} else {
198+
// Just return
199+
}
200+
}
201+
202+
/// <summary>
203+
/// Parses the ladder and profile information out of the given account URL into the given config.
204+
/// The given config is only modified if the URL parses correctly.
205+
/// </summary>
206+
/// <param name="url">A Blizzard ladder URL of the form: https://starcraft2.com/[Locale]/profile/[RegionId]/[RealmId]/[ProfileId]/ladders?ladderId=[LadderId]</param>
207+
/// <param name="config">The config to populate</param>
208+
/// <returns>Returns true if the URL was the right format and was successfully parsed. False otherwise.</returns>
209+
private bool ParseAccountUrlIntoConfig(String url, ReaderConfig config) {
210+
String[] regionIdMap = new String[] { "", "US", "EU", "KO", "", "CN" };
211+
const String ladderIdRegex = "\\/profile\\/([0-9]{1})\\/([0-9]{1})\\/([0-9]*)\\/ladders\\?ladderId=([0-9]*)";
212+
Match match = Regex.Match(url, ladderIdRegex, RegexOptions.None, new TimeSpan(0, 0, 5));
213+
if (match.Success) {
214+
// There should be 5 groups (4 + the full match at [0])
215+
if (match.Groups.Count != 5)
216+
return false;
217+
218+
long regionId = -1;
219+
if (!long.TryParse(match.Groups[1].Value, out regionId))
220+
return false;
221+
if (regionId >= regionIdMap.Length) {
222+
Console.WriteLine("Unknown RegionId in the URL: " + regionId);
223+
return false;
224+
}
225+
226+
int realmId = -1;
227+
if (!int.TryParse(match.Groups[2].Value, out realmId))
228+
return false;
229+
230+
long profileId = -1;
231+
if (!long.TryParse(match.Groups[3].Value, out profileId))
232+
return false;
233+
234+
long ladderId = -1;
235+
if (!long.TryParse(match.Groups[4].Value, out ladderId))
236+
return false;
237+
238+
// Fill out the config
239+
config.RegionId = regionIdMap[regionId];
240+
config.RealmId = realmId;
241+
config.ProfileId = profileId;
242+
config.LadderId = ladderId;
243+
244+
return true;
245+
} else {
246+
// No matches found - the URL is probably not formatted correctly
247+
return false;
248+
}
249+
}
250+
251+
/// <summary>
252+
/// Asks the user to enter one of the given options and returns what they picked.
253+
/// </summary>
254+
/// <param name="options">An array of options for the user to pick from.</param>
255+
/// <returns>Returns one of the options in the given array.</returns>
256+
private String GetInputFromUser(String[] options) {
257+
String optionsLine = " (";
258+
int index = 0;
259+
foreach (String option in options) {
260+
optionsLine += options[index] + "/";
261+
index++;
262+
}
263+
optionsLine = optionsLine.Substring(0, optionsLine.Length - 1) + "): ";
264+
265+
String input = "";
266+
while (!options.Contains(input)) {
267+
Console.Write(optionsLine);
268+
input = Console.ReadLine();
269+
}
270+
271+
return input;
272+
}
273+
}
274+
}

Config.json.example renamed to Sc2MmrReader/Config.json.example

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
{
2-
"Version": 1,
32
"MsPerRead": 5000,
43
"DataDirectory": "",
54
"MmrFilePath": "mmr.txt",
65
"RegionId": "US",
7-
"RealmId": 1,
86
"ProfileId": 1986271,
97
"LadderId": 274006,
108
"ClientId": "GetThisFromTheBlizzardDeveloperApiSite",

Sc2MmrReader/ConfigFileManager.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.IO;
3+
using System.Web.Script.Serialization;
4+
5+
namespace Sc2MmrReader {
6+
/// <summary>
7+
/// Provides methods for reading and writing the config file.
8+
/// </summary>
9+
public class ConfigFileManager {
10+
/// <summary>
11+
/// Reads the configuration file at the given path.
12+
/// </summary>
13+
/// <param name="path">A path to a JSON config file to read. The JSON file must exactly match the ReaderConfig class.</param>
14+
/// <returns>Returns the configuration read in the file or null if there was an error. The error is printed to stdout as well.</returns>
15+
public static ReaderConfig ReadConfigFile(String path) {
16+
String configFileText = null;
17+
try {
18+
configFileText = File.ReadAllText(path);
19+
} catch (IOException ex) {
20+
Console.WriteLine("Unable to open the config file at " + path);
21+
Console.WriteLine("Does the file exist?");
22+
Console.WriteLine("Exception: " + ex.Message);
23+
return null;
24+
}
25+
26+
JavaScriptSerializer deserializer = new JavaScriptSerializer();
27+
ReaderConfig configFile = null;
28+
29+
try {
30+
configFile = deserializer.Deserialize<ReaderConfig>(configFileText);
31+
} catch (Exception ex) {
32+
Console.WriteLine("Could not deserialize the config file. Is the format correct? See Config.json.example for correct usage.");
33+
Console.WriteLine("Exception: " + ex.Message);
34+
if (ex.InnerException != null) {
35+
Console.WriteLine("Inner Exception: " + ex.InnerException.Message);
36+
}
37+
}
38+
39+
return configFile;
40+
}
41+
42+
/// <summary>
43+
/// Saves the given config file to the given path.
44+
/// </summary>
45+
/// <param name="config">The config to write.</param>
46+
/// <param name="path">The path to save the config file to</param>
47+
/// <returns>Returns true if it succeeded, false if there was an error.</returns>
48+
public static bool SaveConfigFile(ReaderConfig config, String path) {
49+
JavaScriptSerializer serializer = new JavaScriptSerializer();
50+
String configString = null;
51+
try {
52+
configString = serializer.Serialize(config);
53+
} catch (Exception ex) {
54+
Console.WriteLine("Could not serialize the given config.");
55+
Console.WriteLine("Exception: " + ex.Message);
56+
if (ex.InnerException != null) {
57+
Console.WriteLine("Inner Exception: " + ex.InnerException.Message);
58+
}
59+
60+
return false;
61+
}
62+
63+
// Do a poor mans prettify since JavaScriptSerializer doesn't support
64+
// formatting the output. This could be improved by using an actual library like JSON.net
65+
configString = configString.Insert(configString.IndexOf("{") + 1, Environment.NewLine + "\t");
66+
configString = configString.Insert(configString.LastIndexOf("}"), Environment.NewLine);
67+
configString = configString.Replace("\":", "\": ");
68+
configString = configString.Replace(",\"", "," + Environment.NewLine + "\t\"");
69+
70+
// Write it to a file
71+
try {
72+
File.WriteAllText(path, configString);
73+
} catch (Exception ex) {
74+
Console.WriteLine("Could not write the config file to " + path);
75+
Console.WriteLine("Exception: " + ex.Message);
76+
if (ex.InnerException != null) {
77+
Console.WriteLine("Inner Exception: " + ex.InnerException.Message);
78+
}
79+
}
80+
81+
return true;
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)