diff --git a/.gitignore b/.gitignore
index 554dfab..1de2126 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,4 +66,4 @@ yarn-error.log*
/WeeklyRotators/bin
/WeeklyRotators/config.toml
/WeeklyRotators/logging
-.env
\ No newline at end of file
+.env
diff --git a/Authenticate/Authenticate.csproj b/Authenticate/Authenticate.csproj
new file mode 100644
index 0000000..e0d3cd2
--- /dev/null
+++ b/Authenticate/Authenticate.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/Authenticate/GenerateToken.cs b/Authenticate/GenerateToken.cs
new file mode 100644
index 0000000..2861751
--- /dev/null
+++ b/Authenticate/GenerateToken.cs
@@ -0,0 +1,74 @@
+using Nett;
+
+class Program{
+ static async Task Main(){
+
+ // Gets all the data from TOML File
+ var table = Toml.ReadFile("config.toml");
+
+ string? ClientID = table.ClientID;
+ string? ClientSecret = table.ClientSecret;
+
+ try{
+
+ string? Code = table.Code;
+ string? AccessToken = table.AccessToken;
+ // No value for Access token is not in toml file
+ if (string.IsNullOrEmpty(AccessToken)){
+ var (accessToken, refreshToken) = await Tokenizer.GetAccessToken(ClientID, ClientSecret, Code);
+
+ Console.WriteLine("Access Token: " + accessToken.Token);
+ Console.WriteLine("Refresh Token: " + refreshToken.Token);
+ // Create a TomlObject with the access token string value
+ //var accessTokenTomlObject = Toml.Create(accessToken.Token);
+
+ //var config = Toml.ReadFile("config.toml");
+ table.AccessToken = accessToken.Token;
+ table.AccessTokenExpiresAt = accessToken.Expiration;
+
+ table.RefreshToken = refreshToken.Token;
+ table.RefreshTokenExpiresAt = refreshToken.Expiration;
+
+ // Console.WriteLine(accessTokenTomlObject);
+
+ // Update the AccessToken value in the TOML table
+ //table["AccessToken"] = accessTokenTomlObject;
+ //table["AccessToken"] = accessToken.Token;
+ Toml.WriteFile(table, "config.toml");
+ }
+ else{
+ DateTime AccessTokenExpiresAt = table.AccessTokenExpiresAt;
+
+ string? RefreshToken = table.RefreshToken;
+
+ if (Tokenizer.IsTokenValid(AccessTokenExpiresAt) == false){
+ var (accessToken, refreshToken) = await Tokenizer.RefreshAccessToken(ClientID, ClientSecret, RefreshToken);
+
+ table.AccessToken = accessToken.Token;
+ table.AccessTokenExpiresAt = accessToken.Expiration;
+
+ table.RefreshToken = refreshToken.Token;
+ table.RefreshTokenExpiresAt = refreshToken.Expiration;
+
+ Toml.WriteFile(table, "config.toml");
+ }
+ }
+ }
+ catch (Exception ex){
+ // Handle exceptions
+ Console.WriteLine("An error occurred: " + ex.Message);
+ }
+ }
+}
+
+class Config{
+ public string? APIKey { get; set; }
+ public string? ClientID { get; set; }
+ public string? ClientSecret { get; set; }
+ public string? Code { get; set; }
+ public string? AccessToken { get; set; }
+ public DateTime AccessTokenExpiresAt { get; set; }
+ public string? RefreshToken { get; set; }
+ public DateTime RefreshTokenExpiresAt { get; set; }
+
+}
\ No newline at end of file
diff --git a/Authenticate/Workers/Tokenizer.cs b/Authenticate/Workers/Tokenizer.cs
new file mode 100644
index 0000000..d077fe8
--- /dev/null
+++ b/Authenticate/Workers/Tokenizer.cs
@@ -0,0 +1,137 @@
+using Newtonsoft.Json;
+
+public class Tokenizer{
+ public static async Task<(AccessToken accessToken, RefreshToken refreshToken)> GetAccessToken(string? ClientID, string? ClientSecret, string? code){
+
+ using (HttpClient httpClient = new HttpClient()){
+ try{
+ if (ClientID == null || ClientSecret == null || code == null){
+ throw new Exception("Failed to parse JSON response.");
+ }
+
+ var requestContent = new FormUrlEncodedContent(new Dictionary{
+ { "grant_type", "authorization_code" },
+ { "client_id", ClientID},
+ { "client_secret", ClientSecret},
+ { "code", code}
+ });
+
+ var response = await httpClient.PostAsync("https://www.bungie.net/Platform/App/OAuth/token/", requestContent);
+
+ if (response.IsSuccessStatusCode){
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var responseContentDictionary = ParseTokenResponse(responseContent);
+ (AccessToken accessToken, RefreshToken refreshToken) = SetAccessToken(responseContentDictionary);
+ return (accessToken, refreshToken);
+
+ }
+ else{
+ throw new Exception($"Failed to get access token: {response.StatusCode} - {response.ReasonPhrase}");
+ }
+ }
+ catch (HttpRequestException ex){
+ Console.WriteLine($"Error: {ex.Message}");
+ return (new AccessToken(), new RefreshToken());
+ }
+ }
+ }
+
+ private static Dictionary ParseTokenResponse(string responseContent){
+
+ // Parse the JSON response to extract the access token, refresh token, and expiration time
+ // For simplicity, assuming the response is a JSON object with fields "access_token", "refresh_token", and "expires_in"
+ // You might need to use a JSON parsing library like Newtonsoft.Json for proper parsing
+ // Example parsing logic (replace with actual parsing)
+
+ // Parse the JSON string into a dictionary
+ Dictionary? acessTokenDict = JsonConvert.DeserializeObject>(responseContent);
+
+ if (acessTokenDict == null){
+ // Throw an exception if JSON parsing fails
+ throw new Exception("Failed to parse JSON response.");
+ }
+
+ return acessTokenDict;
+ }
+
+ private static (AccessToken accessToken, RefreshToken refreshToken) SetAccessToken(Dictionary accessTokenDict){
+ AccessToken accessToken = new AccessToken{
+ Token = accessTokenDict["access_token"],
+ TokenType = accessTokenDict["token_type"],
+ ExpiresIn = int.Parse(accessTokenDict["expires_in"]),
+
+ };
+ accessToken.Expiration = DateTime.UtcNow.AddSeconds(accessToken.ExpiresIn);
+
+
+ RefreshToken refreshToken = new RefreshToken{
+ Token = accessTokenDict["refresh_token"],
+ TokenType = accessTokenDict["token_type"],
+ ExpiresIn = int.Parse(accessTokenDict["refresh_expires_in"]),
+ };
+ refreshToken.Expiration = DateTime.UtcNow.AddSeconds(refreshToken.ExpiresIn);
+
+ return (accessToken, refreshToken);
+ }
+
+ public static bool IsTokenValid(DateTime accessTokenExpiresAt){
+ if (DateTime.UtcNow >= accessTokenExpiresAt){
+ return false;
+ }
+ return true;
+ }
+
+ public static async Task<(AccessToken accessToken, RefreshToken refreshToken)> RefreshAccessToken(string? ClientID, string? ClientSecret, string? RefreshToken){
+
+ using (HttpClient httpClient = new HttpClient()){
+ try{
+
+ if (ClientID == null || ClientSecret == null || RefreshToken == null){
+ throw new Exception("Failed to parse JSON response.");
+ }
+
+ var requestContent = new FormUrlEncodedContent(new Dictionary{
+ { "grant_type", "refresh_token" },
+ { "client_id", ClientID },
+ { "client_secret", ClientSecret },
+ { "refresh_token", RefreshToken }
+
+ });
+
+ var response = await httpClient.PostAsync("https://www.bungie.net/Platform/App/OAuth/token/", requestContent);
+
+ if (response.IsSuccessStatusCode){
+ var responseContent = await response.Content.ReadAsStringAsync();
+ Console.WriteLine(responseContent);
+ var responseContentDictionary = ParseTokenResponse(responseContent);
+ (AccessToken accessToken, RefreshToken refreshToken) = SetAccessToken(responseContentDictionary);
+ return (accessToken, refreshToken);
+ }
+ else{
+ throw new Exception($"Failed to get access token: {response.StatusCode} - {response.ReasonPhrase}");
+ }
+ }
+ catch (HttpRequestException ex){
+ Console.WriteLine($"Error: {ex.Message}");
+ return (new AccessToken(), new RefreshToken());
+ }
+ }
+ }
+
+ public class AccessToken{
+ public string? Token { get; set; }
+ public string? TokenType { get; set; }
+ public int ExpiresIn { get; set; }
+ public string? MembershipId { get; set; }
+ public DateTime Expiration { get; set; }
+
+ }
+
+ public class RefreshToken{
+ public string? Token { get; set; }
+ public string? TokenType { get; set; }
+ public int ExpiresIn { get; set; }
+ public string? MembershipId { get; set; }
+ public DateTime Expiration { get; set; }
+ }
+}
diff --git a/guardians-central.sln b/guardians-central.sln
index 280c51e..0cfe2c0 100644
--- a/guardians-central.sln
+++ b/guardians-central.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "database", "database\database.csproj", "{E1DD000A-8F94-43D3-865B-CBBA22180F15}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authenticate", "Authenticate\Authenticate.csproj", "{A3F99E5C-9F29-46A8-A474-5226A5FF068C}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeeklyRotators", "WeeklyRotators\WeeklyRotators.csproj", "{E1DD000A-8F94-43D3-865B-CBBA22180F15}"
EndProject
Global
@@ -17,6 +19,10 @@ Global
{E1DD000A-8F94-43D3-865B-CBBA22180F15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1DD000A-8F94-43D3-865B-CBBA22180F15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1DD000A-8F94-43D3-865B-CBBA22180F15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A3F99E5C-9F29-46A8-A474-5226A5FF068C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A3F99E5C-9F29-46A8-A474-5226A5FF068C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3F99E5C-9F29-46A8-A474-5226A5FF068C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A3F99E5C-9F29-46A8-A474-5226A5FF068C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE