public static void Main(string[] args)
        {
            // two things it can do: get new access token, or refresh existing one
            // if a data.ini file exists, use that and refresh
            // if not exists, create new
            var dataIniFileName = args.FirstOrDefault() ?? DefaultDataIniFileName;
            var data            = OAuthData.TryReadData(dataIniFileName);
            var t = data == null?DoNew(dataIniFileName) : DoExisting(data, dataIniFileName);

            t.GetAwaiter().GetResult();
        }
        public static OAuthData Parse(string[] lines)
        {
            var data = new OAuthData();

            foreach (var line in lines)
            {
                if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#') || line.StartsWith("//"))
                {
                    continue;
                }
                var tmp = line.Split('=', 2);
                if (tmp.Length != 2)
                {
                    continue;
                }
                var key = tmp[0].Trim();
                var val = tmp[1].Trim();
                switch (key)
                {
                case nameof(OAuthBaseUrl):
                    data.OAuthBaseUrl = val;
                    break;

                case nameof(OAuthClientId):
                    data.OAuthClientId = val;
                    break;

                case nameof(OAuthClientSecret):
                    data.OAuthClientSecret = val;
                    break;

                case nameof(LocalPort):
                    data.LocalPort = int.Parse(val);
                    break;

                case nameof(Scopes):
                    data.Scopes = val;
                    break;

                case nameof(AccessToken):
                    data.AccessToken = val;
                    break;

                case nameof(RefreshToken):
                    data.RefreshToken = val;
                    break;
                }
            }

            return(data);
        }
        private static async Task DoNew(string dataIniFile)
        {
            Console.WriteLine($"No {dataIniFile} file found, generating a new one!");

            // port
            var portStr = Prompt("Local free port to use (leave empty to pick at random)");
            var port    = string.IsNullOrWhiteSpace(portStr) ? GetRandomFreePort() : int.Parse(portStr);

            // local callback url
            var defaultCallbackUrl = GetCallbackUrlForPort(port, DefaultCallbackUrlLocalPath);

            Console.WriteLine(
                $"Lets make up a callback/redirect url, enter if if you already have one or leave empty to use {defaultCallbackUrl}");
            var    callbackUrlLocalPath = Prompt(GetCallbackUrlForPort(port, "/"), false);
            string callbackUrl;

            if (string.IsNullOrWhiteSpace(callbackUrlLocalPath))
            {
                callbackUrl          = defaultCallbackUrl;
                callbackUrlLocalPath = DefaultCallbackUrlLocalPath;
            }
            else
            {
                callbackUrl = GetCallbackUrlForPort(port, $"/{callbackUrlLocalPath.Trim()}");
            }

            Console.WriteLine(
                $"Create a new OAuth application with the following callback/redirect url: {callbackUrl}");

            // oauth app details
            var baseUrl      = Prompt("OAuth Base Url", OsuOAuthBaseUrl);
            var clientId     = Prompt("OAuth Client ID");
            var clientSecret = Prompt("OAuth Client Secret");
            var scopes       = Prompt("OAuth scopes");
            var randomState  = Guid.NewGuid().ToString();
            var authUrl      = BuildAuthUrl(baseUrl, clientId, callbackUrl, scopes, randomState);
            var listener     = new OAuthLocalhostCallbackListener(port, randomState, callbackUrlLocalPath);

            listener.Start();
            Console.WriteLine(
                "opening a browser window, follow steps there till it tells you to close the thing again and then come back here");
            Console.WriteLine("Waiting for valid response...");
            OpenBrowser(authUrl);
            var authCode = await listener.WaitForCode();

            Console.WriteLine("OAuth response received, now turning into an access token...");
            var token = await GetTokenFromAuthCode(baseUrl, clientId, clientSecret, callbackUrl, authCode);

            Console.WriteLine("Access token received, saving to disk...");

            var data = new OAuthData
            {
                OAuthBaseUrl      = baseUrl,
                OAuthClientId     = clientId,
                OAuthClientSecret = clientSecret,
                LocalPort         = port,
                Scopes            = scopes,
                AccessToken       = token.AccessToken,
                RefreshToken      = token.RefreshToken
            };

            await DoEnding(data, dataIniFile);
        }