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