diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index cd967fc..0000000
--- a/.dockerignore
+++ /dev/null
@@ -1,25 +0,0 @@
-**/.dockerignore
-**/.env
-**/.git
-**/.gitignore
-**/.project
-**/.settings
-**/.toolstarget
-**/.vs
-**/.vscode
-**/.idea
-**/*.*proj.user
-**/*.dbmdl
-**/*.jfm
-**/azds.yaml
-**/bin
-**/charts
-**/docker-compose*
-**/Dockerfile*
-**/node_modules
-**/npm-debug.log
-**/obj
-**/secrets.dev.yaml
-**/values.dev.yaml
-LICENSE
-README.md
\ No newline at end of file
diff --git a/API/API.csproj b/API/API.csproj
new file mode 100644
index 0000000..dacc57b
--- /dev/null
+++ b/API/API.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net9.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SQLiteEF/Context.cs b/SQLiteEF/Context.cs
new file mode 100644
index 0000000..d7aeff1
--- /dev/null
+++ b/SQLiteEF/Context.cs
@@ -0,0 +1,33 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+
+namespace SQLiteEF;
+
+public class Context(IConfiguration configuration) : DbContext
+{
+ public DbSet Players { get; set; }
+ public DbSet Games { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSqlite(configuration.GetConnectionString("DefaultConnection"));
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity()
+ .HasMany(p => p.Games)
+ .WithMany(g => g.PlayedBy);
+ modelBuilder.Entity()
+ .Navigation(p => p.Games)
+ .AutoInclude();
+ modelBuilder.Entity()
+ .HasOne(p => p.Player)
+ .WithMany(p => p.TrackedTimes)
+ .OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity()
+ .HasOne(p => p.Game)
+ .WithMany(g => g.TrackedTimes)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
\ No newline at end of file
diff --git a/SQLiteEF/Game.cs b/SQLiteEF/Game.cs
new file mode 100644
index 0000000..04842c3
--- /dev/null
+++ b/SQLiteEF/Game.cs
@@ -0,0 +1,12 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace SQLiteEF;
+
+[PrimaryKey("AppId")]
+public class Game(ulong appId, string name)
+{
+ public ulong AppId { get; init; } = appId;
+ public string Name { get; init; } = name;
+ public ICollection PlayedBy { get; init; } = null!;
+ public ICollection TrackedTimes { get; init; } = null!;
+}
\ No newline at end of file
diff --git a/SQLiteEF/IUpdateable.cs b/SQLiteEF/IUpdateable.cs
new file mode 100644
index 0000000..80a3500
--- /dev/null
+++ b/SQLiteEF/IUpdateable.cs
@@ -0,0 +1,6 @@
+namespace SQLiteEF;
+
+public interface IUpdateable
+{
+ public DateTime UpdatedAt { get; set; }
+}
\ No newline at end of file
diff --git a/SQLiteEF/TrackedTime.cs b/SQLiteEF/TrackedTime.cs
new file mode 100644
index 0000000..c03095a
--- /dev/null
+++ b/SQLiteEF/TrackedTime.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace SQLiteEF;
+
+[PrimaryKey("TimeStamp")]
+public class TrackedTime
+{
+ public Game Game { get; init; }
+ public Player Player { get; init; }
+ public DateTime TimeStamp { get; init; }
+ public ulong TimePlayed { get; init; }
+
+ public TrackedTime(Game game, Player player, ulong timePlayed, DateTime? timeStamp = null)
+ {
+ this.Game = game;
+ this.Player = player;
+ this.TimeStamp = timeStamp??DateTime.Now;
+ this.TimePlayed = timePlayed;
+ }
+
+ ///
+ /// EF CORE
+ ///
+ internal TrackedTime(ulong timePlayed, DateTime timeStamp)
+ {
+ this.TimePlayed = timePlayed;
+ this.TimeStamp = timeStamp;
+ }
+}
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/Game.cs b/SteamApiWrapper/ReturnTypes/Game.cs
new file mode 100644
index 0000000..df38135
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/Game.cs
@@ -0,0 +1,3 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public record Game(ulong appid, ulong playtime_forever, string name);
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/GetOwnedGames.cs b/SteamApiWrapper/ReturnTypes/GetOwnedGames.cs
new file mode 100644
index 0000000..fa16ba6
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/GetOwnedGames.cs
@@ -0,0 +1,3 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public record GetOwnedGames(uint game_count, Game[] games) : IReturnType;
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/GetPlayerSummaries.cs b/SteamApiWrapper/ReturnTypes/GetPlayerSummaries.cs
new file mode 100644
index 0000000..6686720
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/GetPlayerSummaries.cs
@@ -0,0 +1,3 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public record GetPlayerSummaries(Player[] players) : IReturnType;
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/GetRecentlyPlayedGames.cs b/SteamApiWrapper/ReturnTypes/GetRecentlyPlayedGames.cs
new file mode 100644
index 0000000..c5709e0
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/GetRecentlyPlayedGames.cs
@@ -0,0 +1,3 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public record GetRecentlyPlayedGames(uint total_count, Game[] games) : IReturnType;
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/GetSupportedAPIList.cs b/SteamApiWrapper/ReturnTypes/GetSupportedAPIList.cs
new file mode 100644
index 0000000..b58b02f
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/GetSupportedAPIList.cs
@@ -0,0 +1,3 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public record GetSupportedAPIList(SupportedAPIInterface[] Interfaces) : IReturnType;
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/IReturnType.cs b/SteamApiWrapper/ReturnTypes/IReturnType.cs
new file mode 100644
index 0000000..5d2c83e
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/IReturnType.cs
@@ -0,0 +1,6 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public interface IReturnType
+{
+
+}
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/Player.cs b/SteamApiWrapper/ReturnTypes/Player.cs
new file mode 100644
index 0000000..b563461
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/Player.cs
@@ -0,0 +1,27 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public record Player(ulong steamid,
+ CommunityVisibilityState communityvisibilitystate,
+ bool profilestate,
+ string personaname,
+ string profileurl,
+ string avatar,
+ PersonaState personastate,
+ string? realname);
+
+public enum PersonaState : byte
+{
+ Offline = 0,
+ Online = 1,
+ Busy = 2,
+ Away = 3,
+ Snooze = 4,
+ LookingToTrade = 5,
+ LookingToPlay = 6
+}
+
+public enum CommunityVisibilityState : byte
+{
+ Hidden = 1,
+ Public = 3
+}
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/SupportedAPIInterface.cs b/SteamApiWrapper/ReturnTypes/SupportedAPIInterface.cs
new file mode 100644
index 0000000..d6a148c
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/SupportedAPIInterface.cs
@@ -0,0 +1,13 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public struct SupportedAPIInterface(string name, SupportedAPIMethod[]? methods = null)
+{
+ public readonly string Name = name;
+ public readonly SupportedAPIMethod[]? Methods = methods;
+
+ public override string ToString() => Name;
+
+ public static SupportedAPIInterface IPlayerService = new("IPlayerService");
+ public static SupportedAPIInterface ISteamWebAPIUtil = new("ISteamWebAPIUtil");
+ public static SupportedAPIInterface ISteamUser = new("ISteamUser");
+}
\ No newline at end of file
diff --git a/SteamApiWrapper/ReturnTypes/SupportedAPIMethod.cs b/SteamApiWrapper/ReturnTypes/SupportedAPIMethod.cs
new file mode 100644
index 0000000..3115aa9
--- /dev/null
+++ b/SteamApiWrapper/ReturnTypes/SupportedAPIMethod.cs
@@ -0,0 +1,7 @@
+namespace SteamApiWrapper.ReturnTypes;
+
+public struct SupportedAPIMethod(string name, int version)
+{
+ public string Name = name;
+ public int Version = version;
+}
\ No newline at end of file
diff --git a/SteamApiWrapper/SteamApiWrapper.cs b/SteamApiWrapper/SteamApiWrapper.cs
new file mode 100644
index 0000000..62c7fde
--- /dev/null
+++ b/SteamApiWrapper/SteamApiWrapper.cs
@@ -0,0 +1,106 @@
+using Newtonsoft.Json.Linq;
+using SteamApiWrapper.ReturnTypes;
+
+namespace SteamApiWrapper;
+
+public static class SteamApiWrapper
+{
+ public const string ApiUrl = "http://api.steampowered.com/";
+ private static string _apiKey = string.Empty;
+ private static HttpClient client = new ();
+
+ public static void SetApiKey(string apiKey)
+ {
+ _apiKey = apiKey;
+ }
+
+ private static HttpRequestMessage BuildRequest(SupportedAPIInterface apiInterface, string methodName, string version = "v0001", Dictionary? opts = null)
+ {
+ string url = $"{ApiUrl}/{apiInterface}/{methodName}/{version}?key={_apiKey}&format=json";
+ if (opts != null)
+ foreach ((string key, string value) in opts)
+ url += $"&{key}={value}";
+
+ HttpRequestMessage ret = new(HttpMethod.Get, url);
+ return ret;
+ }
+
+ public static GetOwnedGames GetOwnedGames(ulong steamid)
+ {
+ HttpRequestMessage request = BuildRequest(SupportedAPIInterface.IPlayerService, "GetOwnedGames", opts: new()
+ {
+ {"steamid", steamid.ToString()},
+ {"include_appinfo", "true"},
+ {"include_played_free_games", "true"}
+ });
+ try
+ {
+ HttpResponseMessage response = client.Send(request);
+ response.EnsureSuccessStatusCode();
+ JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ return jObj["response"]?.ToObject()??new(0, []);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+ return new(0, []);
+ }
+ }
+
+ public static GetPlayerSummaries GetPlayerSummaries(ulong[] steamids)
+ {
+ HttpRequestMessage request = BuildRequest(SupportedAPIInterface.ISteamUser, "GetPlayerSummaries", "v0002", opts: new()
+ {
+ {"steamids", string.Join(',', steamids)}
+ });
+ try
+ {
+ HttpResponseMessage response = client.Send(request);
+ response.EnsureSuccessStatusCode();
+ JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ return jObj["response"]?["players"]?.ToObject()??new([]);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+ return new([]);
+ }
+ }
+
+ public static GetRecentlyPlayedGames GetRecentlyPlayedGames(ulong steamid)
+ {
+ HttpRequestMessage request = BuildRequest(SupportedAPIInterface.IPlayerService, "GetRecentlyPlayedGames", opts: new()
+ {
+ {"steamid", steamid.ToString()}
+ });
+ try
+ {
+ HttpResponseMessage response = client.Send(request);
+ response.EnsureSuccessStatusCode();
+ JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ return jObj["response"]?.ToObject()??new(0, []);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+ return new(0, []);
+ }
+ }
+
+ public static GetSupportedAPIList GetSupportedAPIList()
+ {
+ HttpRequestMessage request = BuildRequest(SupportedAPIInterface.ISteamWebAPIUtil, "GetSupportedAPIList");
+ try
+ {
+ HttpResponseMessage response = client.Send(request);
+ response.EnsureSuccessStatusCode();
+ JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ return jObj["apilist"]?.ToObject()??new([]);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+ return new([]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SteamApiWrapper/SteamApiWrapper.csproj b/SteamApiWrapper/SteamApiWrapper.csproj
new file mode 100644
index 0000000..728fd98
--- /dev/null
+++ b/SteamApiWrapper/SteamApiWrapper.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net9.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
diff --git a/SteamGameTimeTrack.sln b/SteamGameTimeTrack.sln
index 7713aba..0698c66 100644
--- a/SteamGameTimeTrack.sln
+++ b/SteamGameTimeTrack.sln
@@ -1,6 +1,14 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamGameTimeTrack", "SteamGameTimeTrack\SteamGameTimeTrack.csproj", "{40948C87-098B-48A5-BABA-02395BE797C2}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{92752A41-5C89-47E0-9588-379AC9EFD706}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamApiWrapper", "SteamApiWrapper\SteamApiWrapper.csproj", "{53B416A7-E3B9-4F19-9A71-16CA115BB127}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Run", "Run\Run.csproj", "{959773AD-4671-4FC7-9729-0EE4A971918E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tracker", "Tracker\Tracker.csproj", "{59876ADC-5DF9-4672-8AA2-F6943966ABF5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLiteEF", "SQLiteEF\SQLiteEF.csproj", "{A269EE55-A8E1-4421-9A70-D6F36D162C4D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -8,9 +16,25 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {40948C87-098B-48A5-BABA-02395BE797C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {40948C87-098B-48A5-BABA-02395BE797C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {40948C87-098B-48A5-BABA-02395BE797C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {40948C87-098B-48A5-BABA-02395BE797C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92752A41-5C89-47E0-9588-379AC9EFD706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92752A41-5C89-47E0-9588-379AC9EFD706}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92752A41-5C89-47E0-9588-379AC9EFD706}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92752A41-5C89-47E0-9588-379AC9EFD706}.Release|Any CPU.Build.0 = Release|Any CPU
+ {53B416A7-E3B9-4F19-9A71-16CA115BB127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {53B416A7-E3B9-4F19-9A71-16CA115BB127}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {53B416A7-E3B9-4F19-9A71-16CA115BB127}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {53B416A7-E3B9-4F19-9A71-16CA115BB127}.Release|Any CPU.Build.0 = Release|Any CPU
+ {959773AD-4671-4FC7-9729-0EE4A971918E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {959773AD-4671-4FC7-9729-0EE4A971918E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {959773AD-4671-4FC7-9729-0EE4A971918E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {959773AD-4671-4FC7-9729-0EE4A971918E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {59876ADC-5DF9-4672-8AA2-F6943966ABF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {59876ADC-5DF9-4672-8AA2-F6943966ABF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59876ADC-5DF9-4672-8AA2-F6943966ABF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {59876ADC-5DF9-4672-8AA2-F6943966ABF5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A269EE55-A8E1-4421-9A70-D6F36D162C4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A269EE55-A8E1-4421-9A70-D6F36D162C4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A269EE55-A8E1-4421-9A70-D6F36D162C4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A269EE55-A8E1-4421-9A70-D6F36D162C4D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/SteamGameTimeTrack.sln.DotSettings b/SteamGameTimeTrack.sln.DotSettings
index cc13654..3e26b44 100644
--- a/SteamGameTimeTrack.sln.DotSettings
+++ b/SteamGameTimeTrack.sln.DotSettings
@@ -1,4 +1,5 @@
True
True
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file
diff --git a/SteamGameTimeTrack/API.cs b/SteamGameTimeTrack/API.cs
deleted file mode 100644
index 9e459c7..0000000
--- a/SteamGameTimeTrack/API.cs
+++ /dev/null
@@ -1,260 +0,0 @@
-using System.Net;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Text.RegularExpressions;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace SteamGameTimeTrack;
-
-public class API : IDisposable
-{
- private readonly HttpListener _server = new();
- private Thread listenThread;
- private CancellationTokenSource cts = new();
- private ILogger logger;
- private readonly Tracker parent;
-
- public API(Tracker parent, int port, ILogger logger)
- {
- this.parent = parent;
- this.logger = logger;
- if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- this._server.Prefixes.Add($"http://*:{port}/");
- else
- this._server.Prefixes.Add($"http://localhost:{port}/");
- this.listenThread = new (Listen);
- this.listenThread.Start();
- }
-
- private void Listen()
- {
- this._server.Start();
- foreach (string serverPrefix in this._server.Prefixes)
- logger.LogInformation($"Listening on {serverPrefix}");
- while (this._server.IsListening)
- {
- try
- {
- //cts.CancelAfter(1000);
- HttpListenerContext context = this._server.GetContext();
- //Log($"{context.Request.HttpMethod} {context.Request.Url} {context.Request.UserAgent}");
- Task t = new(() =>
- {
- HandleRequest(context);
- });
- t.Start();
- }
- catch (Exception e)
- {
- this.logger.LogError(e, e.ToString());
- }
- }
- }
-
- private void HandleRequest(HttpListenerContext context)
- {
- HttpListenerRequest request = context.Request;
- HttpListenerResponse response = context.Response;
- if (request.Url!.LocalPath.Contains("favicon"))
- {
- SendResponse(HttpStatusCode.NoContent, response);
- return;
- }
-
- switch (request.HttpMethod)
- {
- case "GET":
- HandleGet(request, response);
- break;
- case "OPTIONS":
- SendResponse(HttpStatusCode.OK, response);
- break;
- default:
- SendResponse(HttpStatusCode.MethodNotAllowed, response);
- break;
- }
- }
-
- private Dictionary GetRequestVariables(string query)
- {
- Dictionary ret = new();
- Regex queryRex = new (@"\?{1}&?([A-z0-9-=]+=[A-z0-9-=]+)+(&[A-z0-9-=]+=[A-z0-9-=]+)*");
- if (!queryRex.IsMatch(query))
- return ret;
- query = query.Substring(1);
- foreach (string keyValuePair in query.Split('&').Where(str => str.Length >= 3))
- {
- string var = keyValuePair.Split('=')[0];
- string val = Regex.Replace(keyValuePair.Substring(var.Length + 1), "%20", " ");
- val = Regex.Replace(val, "%[0-9]{2}", "");
- ret.Add(var, val);
- }
- return ret;
- }
-
-
- private void HandleGet(HttpListenerRequest request, HttpListenerResponse response)
- {
- Dictionary requestVariables = GetRequestVariables(request.Url!.Query);
- string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
- Regex playerInfoRex = new(@"Player(?:\/([0-9]+))?");
- Regex sessionInfoRex = new(@"Player\/([0-9]+|[A-z0-9]+)\/Session(?:\/([0-9]+))?");
- Regex gameTimeInfoRex = new(@"Player\/([0-9]+|[A-z0-9]+)\/GameTime(?:\/([0-9]+))?");
- Regex playerGameInfo = new(@"Player\/([0-9]+|[A-z0-9]+)(?:\/([0-9]+))");
-
- this.logger.LogInformation($"API GET {path}");
-
- if (gameTimeInfoRex.Match(path) is { Success: true } gti && gti.Value.Equals(path))
- {
- if (parent._players.Any(p => p.steamid.Equals(gti.Groups[1].Value)))
- {
- Player player = parent._players.First(p => p.steamid.Equals(gti.Groups[1].Value));
- if (gti.Groups[2].Success && int.TryParse(gti.Groups[2].Value, out int appid)) //Return specific game
- {
- if (player.GameTimes.Any(g => g.appid == appid))
- SendResponse(HttpStatusCode.OK, response, player.GameTimes.First(g => g.appid == appid));
- else
- SendResponse(HttpStatusCode.NotFound, response, $"AppId {appid} not found");
- }
- else //Return all games
- {
- SendResponse(HttpStatusCode.OK, response, player.GameTimes);
- }
- }
- else
- SendResponse(HttpStatusCode.NotFound, response, $"Player {gti.Groups[1].Value} not found");
- }else if (sessionInfoRex.Match(path) is { Success: true } si&& si.Value.Equals(path))
- {
- if (parent._players.Any(p => p.steamid.Equals(si.Groups[1].Value)))
- {
- Player player = parent._players.First(p => p.steamid.Equals(si.Groups[1].Value));
- if (si.Groups[2].Success && int.TryParse(si.Groups[2].Value, out int appid)) //Return specific game
- {
- if (player.Sessions.Any(s => s.AppId == appid))
- SendResponse(HttpStatusCode.OK, response, player.Sessions.Where(s => s.AppId == appid));
- else
- SendResponse(HttpStatusCode.NotFound, response, $"No Sessions for AppId {appid} found");
- }
- else //Return all games
- {
- SendResponse(HttpStatusCode.OK, response, player.Sessions);
- }
- }
- else
- SendResponse(HttpStatusCode.NotFound, response, $"Player {si.Groups[1].Value} not found");
- }else if(playerGameInfo.Match(path) is { Success: true } gi&& gi.Value.Equals(path))
- {
- if (parent._players.Any(p => p.steamid.Equals(gi.Groups[1].Value)))
- {
- Player player = parent._players.First(p => p.steamid.Equals(gi.Groups[1].Value));
- if (gi.Groups[2].Success && int.TryParse(gi.Groups[2].Value, out int appid)) //Return specific game
- {
- JObject x = new();
- if (player.Sessions.Any(s => s.AppId == appid))
- x["Sessions"] = new JArray(player.Sessions.Where(s => s.AppId == appid));
- else
- x["Sessions"] = null;
- if (player.GameTimes.Any(g => g.appid == appid))
- x["GameTime"] = JObject.FromObject(player.GameTimes.First(g => g.appid == appid));
- else
- x["GameTime"] = null;
-
- if(x["Sessions"] != null && x["GameTime"] != null)
- SendResponse(HttpStatusCode.OK, response, x);
- else
- SendResponse(HttpStatusCode.NotFound, response, $"AppId {appid} not found");
- }
- else //Return all games
- {
- SendResponse(HttpStatusCode.BadRequest, response);
- }
- }
- else
- SendResponse(HttpStatusCode.NotFound, response, $"Player {gi.Groups[1].Value} not found");
- }else if (playerInfoRex.Match(path) is { Success: true } pi&& pi.Value.Equals(path))
- {
- if (pi.Groups[1].Success)//Return specific Player
- {
- if(parent._players.Any(p => p.steamid.Equals(pi.Groups[1].Value)))
- SendResponse(HttpStatusCode.OK, response, parent._players.First(p => p.steamid.Equals(pi.Groups[1].Value)));
- else
- SendResponse(HttpStatusCode.NotFound, response, $"Player {pi.Groups[1].Value} not found");
- }
- else//Return all Players
- SendResponse(HttpStatusCode.OK, response, parent._players);
- }else
- SendResponse(HttpStatusCode.NotFound, response, "Path not found.\n" +
- "Valid Paths:\n" +
- "\t/Player/\n" +
- "\t/Player//\n" +
- "\t/Player//GameTime/\n" +
- "\t/Player//Session/\n");
- }
-
- private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null)
- {
- logger.LogInformation($"Response: {statusCode.ToString()}");
- response.StatusCode = (int)statusCode;
- response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
- response.AddHeader("Access-Control-Allow-Methods", "GET");
- response.AddHeader("Access-Control-Max-Age", "5");
- response.AppendHeader("Access-Control-Allow-Origin", "*");
-
- byte[] bytes;
- if (content is not null)
- {
- if (content is string str)
- bytes = Encoding.UTF8.GetBytes(str);
- else if (content is JObject jo)
- bytes = Encoding.UTF8.GetBytes(jo.ToString());
- else
- bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content));
- }
- else
- bytes = Array.Empty();
-
- if (content is not Stream)
- {
- response.ContentType = content?.GetType() == typeof(string) ? "text/plain" : "application/json";
- try
- {
- response.OutputStream.Write(bytes);
- response.OutputStream.Close();
- }
- catch (HttpListenerException e)
- {
- logger.LogError(e, null);
- }
- }
- else if(content is FileStream stream)
- {
- string contentType = stream.Name.Split('.')[^1];
- switch (contentType.ToLower())
- {
- case "gif":
- response.ContentType = "image/gif";
- break;
- case "png":
- response.ContentType = "image/png";
- break;
- case "jpg":
- case "jpeg":
- response.ContentType = "image/jpeg";
- break;
- case "log":
- response.ContentType = "text/plain";
- break;
- }
- stream.CopyTo(response.OutputStream);
- response.OutputStream.Close();
- stream.Close();
- }
- }
-
- public void Dispose()
- {
- ((IDisposable)_server).Dispose();
- }
-}
\ No newline at end of file
diff --git a/SteamGameTimeTrack/Config.cs b/SteamGameTimeTrack/Config.cs
deleted file mode 100644
index 8c5a3a1..0000000
--- a/SteamGameTimeTrack/Config.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Microsoft.Extensions.Logging;
-
-namespace SteamGameTimeTrack;
-
-public struct Config
-{
- public string SteamId, ApiToken;
- public bool TrackFriends;
- public LogLevel LogLevel;
- public int ApiPort;
-
- public override string ToString()
- {
- return $"SteamId: {SteamId}\nTrack Friends: {(TrackFriends ? "yes" : "no")}\nApi-Port: {ApiPort}\nLogLevel: {Enum.GetName(LogLevel)}\nApiToken: ****{ApiToken.Substring(4, ApiToken.Length-4)}****";
- }
-}
\ No newline at end of file
diff --git a/SteamGameTimeTrack/Dockerfile b/SteamGameTimeTrack/Dockerfile
deleted file mode 100644
index 5519b5f..0000000
--- a/SteamGameTimeTrack/Dockerfile
+++ /dev/null
@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base
-WORKDIR /app
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
-ARG BUILD_CONFIGURATION=Release
-WORKDIR /src
-COPY ["SteamGameTimeTrack/SteamGameTimeTrack.csproj", "SteamGameTimeTrack/"]
-RUN dotnet restore "SteamGameTimeTrack/SteamGameTimeTrack.csproj"
-COPY . .
-WORKDIR "/src/SteamGameTimeTrack"
-RUN dotnet build "SteamGameTimeTrack.csproj" -c $BUILD_CONFIGURATION -o /app/build
-
-FROM build AS publish
-ARG BUILD_CONFIGURATION=Release
-RUN dotnet publish "SteamGameTimeTrack.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-ENTRYPOINT ["dotnet", "SteamGameTimeTrack.dll"]
diff --git a/SteamGameTimeTrack/GameTime.cs b/SteamGameTimeTrack/GameTime.cs
deleted file mode 100644
index 8f86705..0000000
--- a/SteamGameTimeTrack/GameTime.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// ReSharper disable InconsistentNaming
-// ReSharper disable MemberCanBePrivate.Global
-
-using Newtonsoft.Json;
-
-namespace SteamGameTimeTrack;
-
-public struct GameTime
-{
- public int appid, playtime_forever;
- public DateTime? GameStarted;
- public string name;
- [JsonIgnore]
- public TimeSpan PlayTime => TimeSpan.FromMinutes(playtime_forever);
- [JsonIgnore]
- public TimeSpan? Session => GameStarted is not null ? DateTime.UtcNow.Subtract(GameStarted.Value) : null;
-
- public GameTime Clone()
- {
- return new GameTime()
- {
- appid = this.appid,
- GameStarted = this.GameStarted,
- name = this.name,
- playtime_forever = this.playtime_forever
- };
- }
-}
\ No newline at end of file
diff --git a/SteamGameTimeTrack/Net.cs b/SteamGameTimeTrack/Net.cs
deleted file mode 100644
index 7a885df..0000000
--- a/SteamGameTimeTrack/Net.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json.Linq;
-
-namespace SteamGameTimeTrack;
-
-public class Net
-{
- private readonly ILogger _logger;
- private readonly string _apiToken;
- private DateTime lastRequest = DateTime.UtcNow;
- private readonly TimeSpan _minTimeBetweenRequests = TimeSpan.FromMilliseconds(10);
-
- public Net(ILogger logger, string apiToken)
- {
- _logger = logger;
- _apiToken = apiToken;
- }
-
- internal HttpResponseMessage MakeRequest(string uri, string? requestId = null)
- {
- if (requestId is null)
- {
- requestId = RandomString(4);
- this._logger.LogDebug($"RequestId: {requestId} for URI {uri}");
- }
- HttpClient client = new();
- uri = uri.Contains("format=json") ? uri : $"{uri}&format=json";
- uri = uri.Contains("key=") ? uri : $"{uri}&key={_apiToken}";
- HttpRequestMessage request = new (HttpMethod.Get, uri);
- TimeSpan wait = this.lastRequest.Add(_minTimeBetweenRequests).Subtract(DateTime.UtcNow);
- if (wait > TimeSpan.Zero)
- {
- this._logger.LogDebug($"Waiting {wait:s\\.fffff's'} before requesting {requestId}...");
- Thread.Sleep(wait);
- }else
- this._logger.LogDebug($"Requesting {requestId}...");
- HttpResponseMessage response = client.Send(request, HttpCompletionOption.ResponseContentRead);
- this.lastRequest = DateTime.UtcNow;
- this._logger.LogDebug($"Request {requestId} -> {response.StatusCode}");
- return response;
- }
-
- internal JObject? MakeRequestGetJObject(string uri, string? requestId = null)
- {
- if (requestId is null)
- {
- requestId = RandomString(4);
- this._logger.LogDebug($"RequestId: {requestId} for URI {uri}");
- }
- HttpResponseMessage response = MakeRequest(uri, requestId);
- if (!response.IsSuccessStatusCode)
- return null;
- this._logger.LogDebug($"Parsing JObject for {requestId}...");
- return JObject.Parse(response.Content.ReadAsStringAsync().Result);
- }
-
- private static string RandomString(int length)
- {
- const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- return new string(Enumerable.Repeat(chars, length)
- .Select(s => s[Random.Shared.Next(s.Length)]).ToArray());
- }
-}
\ No newline at end of file
diff --git a/SteamGameTimeTrack/Player.cs b/SteamGameTimeTrack/Player.cs
deleted file mode 100644
index 22a558c..0000000
--- a/SteamGameTimeTrack/Player.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-// ReSharper disable IdentifierTypo
-// ReSharper disable InconsistentNaming
-// ReSharper disable MemberCanBePrivate.Global
-// ReSharper disable UnassignedField.Global
-// ReSharper disable PropertyCanBeMadeInitOnly.Global
-
-using System.Reflection;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace SteamGameTimeTrack;
-
-public struct Player
-{
- private readonly Net net;
-
- public string steamid { get; init; }
- public bool IsMe { get; init; }
-
- public PersonaStateEnum personastate { get; set; } = PersonaStateEnum.Offline;
- public byte communityvisibilitystate { get; set; } = 0;
- public bool profilestate { get; set; } = false;
- public long lastlogoff { get; set; } = 0;
- public bool? commentpermission { get; set; } = null;
-
- public string? realname { get; set; } = null;
- public string? primaryclanid { get; set; } = null;
- public string? gameserverip { get; set; } = null;
- public string? gameextrainfo { get; set; } = null;
- public string? loccountrycode { get; set; } = null;
- public string? locstatecode { get; set; } = null;
- public string? loccityid { get; set; } = null;
- public string? personaname { get; set; } = null;
- public string? profileurl { get; set; } = null;
- public string? avatar { get; set; } = null;
- public string? avatarmedium { get; set; } = null;
- public string? avatarfull { get; set; } = null;
- public string? gameid { get; set; } = null;
- public long timecreated { get; set; } = 0;
- public long friend_since { get; set; } = 0;
- public HashSet GameTimes = new();
- // ReSharper disable once CollectionNeverQueried.Global
- public List Sessions = new();
-
- [JsonIgnore]
- public DateTime? TimeCreated => DateTime.UnixEpoch.AddSeconds(timecreated);
- [JsonIgnore]
- public DateTime? FriendSince => DateTime.UnixEpoch.AddSeconds(friend_since);
- [JsonIgnore]
- public DateTime? LastLogoff => DateTime.UnixEpoch.AddSeconds(lastlogoff);
-
- [JsonConstructor]
- public Player(string steamid, Net net, bool IsMe = false)
- {
- this.IsMe = IsMe;
- this.net = net;
- this.steamid = steamid;
- }
-
- public override int GetHashCode()
- {
- return steamid.GetHashCode();
- }
-
- public void Export()
- {
- string path = Path.Join("states", steamid);
- if (!Directory.Exists(path))
- Directory.CreateDirectory(path);
- File.WriteAllText(Path.Join(path, $"{DateTime.UtcNow:dd-mm-yy hh-mm-ss}.json"), JsonConvert.SerializeObject(this, Formatting.Indented));
- }
-
- public bool Diffs(Player other, out string diffStr)
- {
- List diffs = new();
- foreach (FieldInfo field in GetType().GetFields())
- {
- object? thisValue = field.GetValue(this);
- object? otherValue = field.GetValue(other);
- if(thisValue is not null && otherValue is not null && !thisValue.Equals(otherValue))
- diffs.Add($"{field.Name} {thisValue} -> {otherValue}");
- }
-
- foreach (PropertyInfo property in GetType().GetProperties())
- {
- object? thisValue = property.GetValue(this);
- object? otherValue = property.GetValue(other);
- if(thisValue is not null && otherValue is not null && !thisValue.Equals(otherValue))
- diffs.Add($"{property.Name} {thisValue} -> {otherValue}");
- }
-
- GameTime[] newGameTimes = other.GameTimes.Except(GameTimes).ToArray();
- foreach (GameTime newGameTime in newGameTimes)
- diffs.Add($"New game {newGameTime.name} {newGameTime.appid} {newGameTime.PlayTime}");
- foreach (GameTime otherGameTime in other.GameTimes)
- // ReSharper disable once PatternAlwaysMatches
- if(this.GameTimes.First(tgt => tgt.appid == otherGameTime.appid) is GameTime thisGameTime && !thisGameTime.GameStarted.Equals(otherGameTime.GameStarted))
- diffs.Add($"Started {otherGameTime.name} {otherGameTime.appid} at {otherGameTime.GameStarted}");
-
- diffStr = !diffs.Any() ? "No diffs" : $"\n\t{string.Join("\n\t", diffs)}";
- return diffs.Any();
- }
-
- public void UpdateGameInfo(GameTime gameTime)
- {
- if (this.GameTimes.Any(gt => gt.appid == gameTime.appid))
- {
- GameTime thisGameTime = this.GameTimes.First(gt => gt.appid == gameTime.appid);
- this.GameTimes.Remove(thisGameTime);
- }
-
- this.GameTimes.Add(gameTime);
- }
-
- internal void GetGames()
- {
- JObject? gamesResult = net.MakeRequestGetJObject($"https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?&steamid={steamid}&include_appinfo=true&include_played_free_games=true");
- if (gamesResult is not null && gamesResult.TryGetValue("response", out JToken? value) && value.SelectToken("games") is not null)
- {
- foreach (JToken game in value["games"]!)
- {
- GameTime gameTime = game.ToObject();
- this.UpdateGameInfo(gameTime);
- }
- }
- }
-
- public Player WithInfo(Player other)
- {
- if (other.gameid is not null && this.gameid is null) //Game started
- {
- if(!this.GameTimes.Any(game => game.appid.ToString().Equals(other.gameid)))
- GetGames();
- GameTime gameTime = this.GameTimes.First(game => game.appid.ToString().Equals(other.gameid));
- this.GameTimes.Remove(gameTime);
- gameTime.GameStarted = DateTime.UtcNow;
- this.GameTimes.Add(gameTime);
- }else if (other.gameid is null && this.gameid is not null) //Game stopped
- {
- if(!this.GameTimes.Any(game => game.appid.ToString().Equals(other.gameid)))
- GetGames();
- GameTime gameTime = this.GameTimes.First(game => game.appid.ToString().Equals(other.gameid));
- this.Sessions.Add(Session.FromGameTime(gameTime));
- this.GameTimes.Remove(gameTime);
- gameTime.GameStarted = null;
- this.GameTimes.Add(gameTime);
- }
-
- return this with
- {
- personastate = other.personastate,
- communityvisibilitystate = other.communityvisibilitystate,
- profilestate = other.profilestate,
- lastlogoff = other.lastlogoff,
- commentpermission = other.commentpermission,
- realname = other.realname,
- primaryclanid = other.primaryclanid,
- gameserverip = other.gameserverip,
- gameextrainfo = other.gameextrainfo,
- loccountrycode = other.loccountrycode,
- locstatecode = other.locstatecode,
- loccityid = other.loccityid,
- personaname = other.personaname,
- profileurl = other.profileurl,
- avatar = other.avatar,
- avatarmedium = other.avatarmedium,
- avatarfull = other.avatarfull,
- gameid = other.gameid
- };
- }
-
- internal void ShuttingDownSaveSessions()
- {
- if (this.gameid is null)
- return;
- string gid = this.gameid;
- GameTime? gameTime = this.GameTimes.FirstOrDefault(game => game.appid.ToString().Equals(gid));
- if (gameTime is null || gameTime.Value.GameStarted is null)
- return;
- this.Sessions.Add(Session.FromGameTime(gameTime.Value));
- this.gameid = null;
- this.Export();
- }
-
- internal class PlayerJsonConverter : JsonConverter
- {
- private Net net;
-
- public PlayerJsonConverter(Net net)
- {
- this.net = net;
- }
-
- public override bool CanConvert(Type objectType)
- {
- return objectType == typeof(Player);
- }
-
- public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- JObject jo = JObject.Load(reader);
- Player tmp = new (jo.Value("steamid")!, net, jo.Value("IsMe"))
- {
- GameTimes = jo["GameTimes"]!.ToObject>()!,
- Sessions = jo["Sessions"]!.ToObject>()!
- };
- return tmp.WithInfo(jo.ToObject());
- }
-
- public override bool CanWrite => false;
-
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
- {
- throw new NotImplementedException();
- }
- }
-}
-
-public enum PersonaStateEnum : byte
-{
- Offline = 0,
- Online = 1,
- Busy = 2,
- Away = 3,
- Snooze = 4,
- LookingToTrade = 5,
- LookingToPlay = 6
-}
\ No newline at end of file
diff --git a/SteamGameTimeTrack/Program.cs b/SteamGameTimeTrack/Program.cs
deleted file mode 100644
index d48a900..0000000
--- a/SteamGameTimeTrack/Program.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// See https://aka.ms/new-console-template for more information
-
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using SteamGameTimeTrack;
-
-Config config;
-
-if (File.Exists("config.json"))
- config = JsonConvert.DeserializeObject(File.ReadAllText("config.json"));
-else
-{
- string? steamid;
- do
- {
- Console.Clear();
- Console.WriteLine("SteamId:");
- steamid = Console.ReadLine();
- } while (steamid is null || steamid.Length < 1);
-
- string? apiKey;
- do
- {
- Console.Clear();
- Console.WriteLine("API-Key:");
- apiKey = Console.ReadLine();
- } while (apiKey is null || apiKey.Length < 1);
-
- LogLevel logLevel;
- string[] levels = Enum.GetNames();
- do
- {
- Console.Clear();
- for (int i = 0; i < levels.Length; i++)
- Console.WriteLine($"{i}) {levels[i]}");
- } while (!int.TryParse(Console.ReadKey().KeyChar.ToString(), out int selectedLevel) || selectedLevel < 0 ||
- selectedLevel >= levels.Length || !Enum.TryParse(levels[selectedLevel], out logLevel));
-
- Console.Clear();
- Console.WriteLine("Track friends? (y/n)");
- bool trackFriends = Console.ReadKey(true).Key == ConsoleKey.Y;
-
- int port;
- do
- {
- Console.Clear();
- Console.WriteLine("API-Port:");
- } while (!int.TryParse(Console.ReadLine(), out port) || port < 1 || port > 65535);
-
- config = new()
- {
- SteamId = steamid,
- ApiToken = apiKey,
- LogLevel = logLevel,
- TrackFriends = trackFriends,
- ApiPort = port
- };
-}
-
-
-
-Tracker t = new (config);
-while(Console.ReadKey(true).Key != ConsoleKey.Q)
- Console.WriteLine("Press q to quit.");
-t.Dispose();
\ No newline at end of file
diff --git a/SteamGameTimeTrack/Session.cs b/SteamGameTimeTrack/Session.cs
deleted file mode 100644
index d9094b2..0000000
--- a/SteamGameTimeTrack/Session.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Newtonsoft.Json;
-
-namespace SteamGameTimeTrack;
-
-public struct Session
-{
- public int AppId;
- public string Name;
- public DateTime SessionStarted;
- public TimeSpan SessionDuration;
- [JsonIgnore] public DateTime SessionEnded => SessionStarted.Add(SessionDuration);
-
- public static Session FromGameTime(GameTime gameTime)
- {
- if (gameTime.GameStarted is null)
- throw new ArgumentNullException(nameof(GameTime.GameStarted));
- return new Session()
- {
- AppId = gameTime.appid,
- Name = gameTime.name,
- SessionStarted = gameTime.GameStarted.Value,
- SessionDuration = gameTime.Session!.Value
- };
- }
-}
\ No newline at end of file
diff --git a/SteamGameTimeTrack/SteamGameTimeTrack.csproj b/SteamGameTimeTrack/SteamGameTimeTrack.csproj
deleted file mode 100644
index 44ad999..0000000
--- a/SteamGameTimeTrack/SteamGameTimeTrack.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- Exe
- net7.0
- enable
- enable
- Linux
-
-
-
-
- .dockerignore
-
-
-
-
-
-
-
-
-
diff --git a/SteamGameTimeTrack/Tracker.cs b/SteamGameTimeTrack/Tracker.cs
deleted file mode 100644
index 1cd8c09..0000000
--- a/SteamGameTimeTrack/Tracker.cs
+++ /dev/null
@@ -1,201 +0,0 @@
-using GlaxLogger;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace SteamGameTimeTrack;
-
-public class Tracker : IDisposable
-{
- private bool _running = true;
- // ReSharper disable once MemberInitializerValueIgnored
- private readonly ILogger _logger = null!;
- internal readonly HashSet _players = new();
- private readonly Net net;
- private readonly Config config;
- private readonly API api;
-
- private readonly TimeSpan _updateFriendsInterval = TimeSpan.FromMinutes(30);
- private readonly TimeSpan _updateGamesInterval = TimeSpan.FromMinutes(60);
- private readonly TimeSpan _updateInfoInterval = TimeSpan.FromSeconds(60);
- private readonly Thread _updateFriendsThread, _updateGamesThread, _updateInfoThread;
-
- public Tracker(Config config)
- {
- this.config = config;
- this._logger = new Logger(config.LogLevel, consoleOut: Console.Out);
- this.net = new(this._logger, config.ApiToken);
- this._logger.LogInformation(config.ToString());
-
- LoadKnownInformation();
- if(!_players.Any(player => player.IsMe))
- _players.Add(new Player(config.SteamId, net, true));
-
- if (config.TrackFriends)
- UpdateFriends();
-
- UpdatePlayerGames();
- UpdatePlayerInfo();
-
- this._updateFriendsThread = new (() =>
- {
- lock(_updateFriendsThread!)
- {
- while (_running)
- {
- _logger.LogInformation($"Waiting {_updateFriendsInterval:g} for next Friends-List-Update. {DateTime.Now.Add(_updateFriendsInterval):HH:mm:ss zz}");
- Monitor.Wait(_updateFriendsThread, _updateFriendsInterval);
- if (!_running)
- break;
- UpdateFriends();
- }
- }
- });
- this._updateFriendsThread.Start();
-
- this._updateGamesThread = new(() =>
- {
- lock(_updateGamesThread!)
- {
- while (_running)
- {
- _logger.LogInformation($"Waiting {_updateGamesInterval:g} for next Player-Games-Update. {DateTime.Now.Add(_updateGamesInterval):HH:mm:ss zz}");
- Monitor.Wait(_updateGamesThread, _updateGamesInterval);
- if (!_running)
- break;
- UpdatePlayerGames();
- }
- }
- });
- this._updateGamesThread.Start();
-
- this._updateInfoThread = new(() =>
- {
- lock(_updateInfoThread!)
- {
- while (_running)
- {
- _logger.LogInformation($"Waiting {_updateInfoInterval:g} for next Player-Info-Update. {DateTime.Now.Add(_updateInfoInterval):HH:mm:ss zz}");
- Monitor.Wait(_updateInfoThread, _updateInfoInterval);
- if (!_running)
- break;
- UpdatePlayerInfo();
- }
- }
- });
- this._updateInfoThread.Start();
-
- this.api = new(this, config.ApiPort, this._logger);
- }
-
- public Tracker(string steamId, string apiToken, bool trackFriends = true, int apiPort = 8888, LogLevel logLevel = LogLevel.Information) : this(new Config()
- {
- ApiToken = apiToken,
- LogLevel = logLevel,
- SteamId = steamId,
- TrackFriends = trackFriends,
- ApiPort = apiPort
- })
- {
- File.WriteAllText("config.json", JsonConvert.SerializeObject(config, Formatting.Indented));
- }
-
- private void UpdateFriends()
- {
- this._logger.LogInformation("Getting Friends-List...");
- JObject? friendsResult = net.MakeRequestGetJObject($"https://api.steampowered.com/ISteamUser/GetFriendList/v0001/?steamid={config.SteamId}&relationship=friend");
- if (friendsResult is not null && friendsResult.TryGetValue("friendslist", out JToken? value) && value.SelectToken("friends") is not null)
- {
- this._logger.LogDebug("Got Friends-List.");
- foreach (JToken friend in value["friends"]!)
- {
- string steamid = friend["steamid"]!.Value()!;
- if(!_players.Any(player => player.steamid.Equals(steamid)))
- this._players.Add(new(steamid, net)
- {
- friend_since = friend["friend_since"]!.Value()
- });
- }
- this._logger.LogInformation($"Got {this._players.Count - 1} friends.");
- }
- }
-
- private void UpdatePlayerGames()
- {
- int i = 1;
- foreach (Player player in this._players)
- {
- _logger.LogInformation($"Getting GameInfo {i++}/{_players.Count} {player.personaname} {player.steamid}");
- player.GetGames();
- }
- }
-
- private void UpdatePlayerInfo()
- {
- this._logger.LogInformation("Getting Player-info...");
- string steamIds = string.Join(',', _players.Select(player => player.steamid));
- JObject? result = net.MakeRequestGetJObject($"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?steamids={steamIds}");
- if (result is not null && result.TryGetValue("response", out JToken? response))
- {
- foreach (JToken player in response["players"]!)
- {
- Player p = player.ToObject();
- UpdatePlayer(p);
- }
- }
- this._logger.LogInformation("Done getting Player-info...");
- }
-
- private void LoadKnownInformation()
- {
- if (!Directory.Exists("states"))
- return;
- foreach (DirectoryInfo subDir in new DirectoryInfo("states").GetDirectories())
- {
- FileInfo? latest = subDir.GetFiles().MaxBy(file => file.CreationTimeUtc);
- if (latest is not null)
- {
- Player player = JsonConvert.DeserializeObject(File.ReadAllText(latest.FullName), new Player.PlayerJsonConverter(net));
- this._players.Add(player);
- }
- }
- }
-
- private void UpdatePlayer(Player player)
- {
- this._logger.LogDebug($"Updating Player {player.personaname} {player.steamid}");
- Player p = this._players.First(pp => pp.steamid.Equals(player.steamid));
- this._players.Remove(p);
- Player updated = p.WithInfo(player);
-
- this._players.Add(updated);
- if (p.Diffs(updated, out string diffStr))
- {
- updated.Export();
- this._logger.LogDebug($"Diffs: {diffStr}");
- }
- }
-
- public void Dispose()
- {
- this._logger.LogInformation("Stopping...");
- this.api.Dispose();
- _running = false;
- lock (_updateInfoThread)
- {
- Monitor.Pulse(_updateInfoThread);
- }
- lock (_updateGamesThread)
- {
- Monitor.Pulse(_updateGamesThread);
- }
- lock (_updateFriendsThread)
- {
- Monitor.Pulse(_updateFriendsThread);
- }
- this._logger.LogInformation("Exporting Player-States...");
- foreach (Player player in this._players)
- player.ShuttingDownSaveSessions();
- this._logger.LogInformation("Done. Bye.");
- }
-}
\ No newline at end of file