diff --git a/.gitignore b/.gitignore
index f400df3..57ac3f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,6 @@ obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
-.idea/
\ No newline at end of file
+.idea/
+db/
+SteamGameTime.db*
\ No newline at end of file
diff --git a/API/API.csproj b/API/API.csproj
index dacc57b..6e6cc3b 100644
--- a/API/API.csproj
+++ b/API/API.csproj
@@ -8,15 +8,26 @@
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
-
+
+
diff --git a/API/Api.cs b/API/Api.cs
index 809fc9e..459f60f 100644
--- a/API/Api.cs
+++ b/API/Api.cs
@@ -1,20 +1,33 @@
+using System.Reflection;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.OpenApi;
using SQLiteEF;
namespace API;
public class Api
{
+ private Tracker _tracker;
public Api(string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
- builder.Services.AddControllers();
+ builder.Services.AddControllers().AddNewtonsoftJson();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
- builder.Services.AddOpenApi();
+ builder.Services.AddOpenApi(options =>
+ {
+ options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0;
+ });
+ builder.Services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod()));
+
+ builder.Services.AddSwaggerGen();
- builder.Services.AddDbContext();
+ builder.Services.AddDbContext(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"), optionsBuilder => optionsBuilder.MigrationsAssembly(Assembly.GetAssembly(GetType())!)));
+
+ builder.Services.AddSingleton();
+ SteamApiWrapper.SteamApiWrapper.SetApiKey(builder.Configuration.GetValue("SteamWebApiKey")!);
WebApplication app = builder.Build();
@@ -22,14 +35,17 @@ public class Api
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
+ app.UseSwagger();
+ app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
-
-
+
app.MapControllers();
+
+ app.Services.CreateScope().ServiceProvider.GetRequiredService().Database.Migrate();
app.Run();
}
diff --git a/API/Controllers/ActionsController.cs b/API/Controllers/ActionsController.cs
new file mode 100644
index 0000000..e21f0b7
--- /dev/null
+++ b/API/Controllers/ActionsController.cs
@@ -0,0 +1,84 @@
+using Microsoft.AspNetCore.Mvc;
+using SQLiteEF;
+using static Microsoft.AspNetCore.Http.StatusCodes;
+
+namespace API.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class ActionsController(Context databaseContext) : ApiController(typeof(ActionsController))
+{
+ [HttpPut("Player/{steamId}")]
+ [ProducesResponseType(Status202Accepted)]
+ [ProducesResponseType(Status500InternalServerError)]
+ public IActionResult AddPlayerToTrack(Tracker tracker, ulong steamId)
+ {
+ Player nPlayer = new (steamId, "", "", "");
+ try
+ {
+ databaseContext.Players.Add(nPlayer);
+ databaseContext.SaveChanges();
+ tracker.ForceLoop();
+ return Accepted(nPlayer);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ return StatusCode(Status500InternalServerError);
+ }
+ }
+
+ [HttpPost("Update/Player/{steamId}/All")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult UpdatePlayer(Tracker tracker, ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ tracker.UpdatePlayer(databaseContext, player);
+ tracker.UpdateOwnedGamesPlayer(databaseContext, player);
+ tracker.UpdateGameTimesPlayer(databaseContext, player);
+ return Ok();
+ }
+
+ [HttpPost("Update/Player/{steamId}/Info")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult UpdatePlayerInfo(Tracker tracker, ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ tracker.UpdatePlayer(databaseContext, player);
+ return Ok();
+ }
+
+ [HttpPost("Update/Player/{steamId}/OwnedGames")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult UpdatePlayerOwnedGames(Tracker tracker, ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ tracker.UpdateOwnedGamesPlayer(databaseContext, player);
+ return Ok();
+ }
+
+ [HttpPost("Update/Player/{steamId}/TimeTracked")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult UpdatePlayerTimeTracked(Tracker tracker, ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ tracker.UpdateGameTimesPlayer(databaseContext, player);
+ return Ok();
+ }
+
+ [HttpPost("Update/All")]
+ [ProducesResponseType(Status202Accepted)]
+ public IActionResult Update(Tracker tracker)
+ {
+ tracker.ForceLoop();
+ return Accepted();
+ }
+}
\ No newline at end of file
diff --git a/API/Controllers/ApiController.cs b/API/Controllers/ApiController.cs
new file mode 100644
index 0000000..683ca38
--- /dev/null
+++ b/API/Controllers/ApiController.cs
@@ -0,0 +1,9 @@
+using log4net;
+using Microsoft.AspNetCore.Mvc;
+
+namespace API.Controllers;
+
+public abstract class ApiController(Type type) : Controller
+{
+ protected ILog Log { get; init; } = LogManager.GetLogger(type);
+}
\ No newline at end of file
diff --git a/API/Controllers/DataController.cs b/API/Controllers/DataController.cs
new file mode 100644
index 0000000..bfcfaac
--- /dev/null
+++ b/API/Controllers/DataController.cs
@@ -0,0 +1,66 @@
+using Microsoft.AspNetCore.Mvc;
+using SQLiteEF;
+using static Microsoft.AspNetCore.Http.StatusCodes;
+
+namespace API.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class DataController(Context databaseContext) : ApiController(typeof(DataController))
+{
+ [HttpGet("Players")]
+ [ProducesResponseType(Status200OK)]
+ public IActionResult GetPlayers()
+ {
+ return Ok(databaseContext.Players.ToArray());
+ }
+
+ [HttpGet("Player/{steamId}")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetPlayer(ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ return Ok(player);
+ }
+
+ [HttpGet("Player/{steamId}/Games")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetGamesPlayedByPlayer(ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ databaseContext.Entry(player).Collection(p => p.Games).Load();
+ return Ok(player.Games.ToArray());
+ }
+
+ [HttpGet("Games")]
+ [ProducesResponseType(Status200OK)]
+ public IActionResult GetGames()
+ {
+ return Ok(databaseContext.Games.ToArray());
+ }
+
+ [HttpGet("Game/{appId}")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetGame(ulong appId)
+ {
+ if (databaseContext.Games.Find(appId) is not { } game)
+ return NotFound();
+ return Ok(game);
+ }
+
+ [HttpGet("Game/{appId}/Players")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetPlayersPlayingGame(ulong appId)
+ {
+ if (databaseContext.Games.Find(appId) is not { } game)
+ return NotFound();
+ databaseContext.Entry(game).Collection(g => g.PlayedBy).Load();
+ return Ok(game.PlayedBy.ToArray());
+ }
+}
\ No newline at end of file
diff --git a/API/Controllers/TimeTrackController.cs b/API/Controllers/TimeTrackController.cs
new file mode 100644
index 0000000..3bcfde9
--- /dev/null
+++ b/API/Controllers/TimeTrackController.cs
@@ -0,0 +1,55 @@
+using Microsoft.AspNetCore.Mvc;
+using SQLiteEF;
+using static Microsoft.AspNetCore.Http.StatusCodes;
+
+namespace API.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class TimeTrackController(Context databaseContext) : ApiController(typeof(TimeTrackController))
+{
+ [HttpGet("{steamId}/{appId}")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetTrackedTimeForApp(ulong steamId, ulong appId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ if (databaseContext.Games.Find(appId) is not { } game)
+ return NotFound();
+ databaseContext.Entry(player).Collection(p => p.TrackedTimes).Load();
+ return Ok(player.TrackedTimes.Where(t => t.Game == game).ToArray());
+ }
+
+ [HttpGet("{steamId}/Total")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetTrackedTimeTotal(ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ databaseContext.Entry(player).Collection(p => p.TrackedTimes).Load();
+ ulong[] times = player.TrackedTimes.GroupBy(t => t.Game)
+ .Select(t => t.MaxBy(time => time.TimePlayed)?.TimePlayed ?? 0)
+ .ToArray();
+ ulong sum = 0;
+ foreach (ulong time in times)
+ sum += time;
+ return Ok(sum);
+ }
+
+ [HttpGet("{steamId}/PerGame")]
+ [ProducesResponseType>(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetTrackedTimeAll(ulong steamId)
+ {
+ if (databaseContext.Players.Find(steamId) is not { } player)
+ return NotFound();
+ databaseContext.Entry(player).Collection(p => p.TrackedTimes).Load();
+ Dictionary trackedTimes = player.TrackedTimes
+ .GroupBy(t => t.Game)
+ .ToDictionary(t => t.Key.AppId, t => t.MaxBy(time => time.TimePlayed)?.TimePlayed??0);
+
+ return Ok(trackedTimes);
+ }
+}
\ No newline at end of file
diff --git a/API/Tracker.cs b/API/Tracker.cs
new file mode 100644
index 0000000..c62d022
--- /dev/null
+++ b/API/Tracker.cs
@@ -0,0 +1,202 @@
+using System.Globalization;
+using log4net;
+using SQLiteEF;
+using SteamApiWrapper.ReturnTypes;
+using SteamGame = SteamApiWrapper.ReturnTypes.Game;
+using Player = SQLiteEF.Player;
+using SteamPlayer = SteamApiWrapper.ReturnTypes.Player;
+using Steam = SteamApiWrapper.SteamApiWrapper;
+
+namespace API;
+
+public class Tracker : IDisposable
+{
+ private readonly Thread _trackerThread;
+ private bool _running = true;
+ private ILog Log { get; } = LogManager.GetLogger(typeof(Tracker));
+ private TimeSpan Interval { get; init; }
+
+ public Tracker(IServiceProvider serviceProvider, IConfiguration configuration)
+ {
+ Interval = TimeSpan.Parse(configuration.GetValue("UpdateInterval")!, CultureInfo.InvariantCulture);
+ _trackerThread = new (TrackerLoop);
+ _trackerThread.Start(serviceProvider);
+ }
+
+ internal void UpdatePlayers(Context context)
+ {
+ Log.Info("Updating Players");
+ Player[] dbPlayers = context.Players.ToArray();
+ SteamPlayer[] summaries = Steam.GetPlayerSummaries(dbPlayers.Select(p => p.SteamId).ToArray());
+ foreach (Player dbPlayer in dbPlayers)
+ {
+ if(summaries.All(summaryPlayer => summaryPlayer.steamid != dbPlayer.SteamId))
+ continue;
+ SteamPlayer summaryPlayer = summaries.First(summaryPlayer => summaryPlayer.steamid == dbPlayer.SteamId);
+
+ try
+ {
+ dbPlayer.Name = summaryPlayer.personaname;
+ dbPlayer.AvatarUrl = summaryPlayer.avatar;
+ dbPlayer.ProfileUrl = summaryPlayer.profileurl;
+ dbPlayer.UpdatedAt = DateTime.UtcNow;
+ context.SaveChanges();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+ }
+
+ internal void UpdatePlayer(Context context, Player player)
+ {
+ SteamPlayer[] summaries = Steam.GetPlayerSummaries([player.SteamId]);
+ if(summaries.All(summaryPlayer => summaryPlayer.steamid != player.SteamId))
+ return;
+ SteamPlayer summaryPlayer = summaries.First(summaryPlayer => summaryPlayer.steamid == player.SteamId);
+
+ try
+ {
+ player.Name = summaryPlayer.personaname;
+ player.AvatarUrl = summaryPlayer.avatar;
+ player.ProfileUrl = summaryPlayer.profileurl;
+ player.UpdatedAt = DateTime.UtcNow;
+ context.SaveChanges();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+
+ internal void UpdateOwnedGames(Context context)
+ {
+ Log.Info("Updating Owned Games");
+ Player[] players = context.Players.ToArray();
+ foreach (Player player in players)
+ {
+ UpdateOwnedGamesPlayer(context, player);
+ }
+ }
+
+ internal void UpdateOwnedGamesPlayer(Context context, Player player)
+ {
+ Log.Debug($"Updating owned games for player {player}");
+ SteamGame[] ownedGames = Steam.GetOwnedGames(player.SteamId);
+ foreach (SteamGame ownedGame in ownedGames)
+ {
+ if (context.Games.Find(ownedGame.appid) is not { } game)
+ {
+ game = new(ownedGame.appid, ownedGame.name);
+ context.Games.Add(game);
+ }
+
+ if (!player.Games.Contains(game))
+ {
+ context.Entry(player).Collection(p => p.Games).Load();
+ player.Games.Add(game);
+ context.Entry(game).Collection(g => g.PlayedBy).Load();
+ game.PlayedBy.Add(player);
+ context.Entry(player).Collection(p => p.TrackedTimes).Load();
+ player.TrackedTimes.Add(new (game, player, ownedGame.playtime_forever));
+ }
+
+ try
+ {
+ context.SaveChanges();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+ }
+
+ internal void UpdateGameTimes(Context context)
+ {
+ Log.Info("Updating Game Times");
+ Player[] players = context.Players.ToArray();
+ foreach (Player player in players)
+ {
+ UpdateGameTimesPlayer(context, player);
+ }
+ }
+
+ internal void UpdateGameTimesPlayer(Context context, Player player)
+ {
+ Log.Debug($"Updating game times for player {player}");
+ GetRecentlyPlayedGames recentlyPlayed = Steam.GetRecentlyPlayedGames(player.SteamId);
+ foreach (SteamGame recentlyPlayedGame in recentlyPlayed.games)
+ {
+ if (context.Games.Find(recentlyPlayedGame.appid) is not { } game)
+ {
+ game = new(recentlyPlayedGame.appid, recentlyPlayedGame.name);
+ context.Games.Add(game);
+ }
+
+ if (!player.Games.Contains(game))
+ {
+ context.Entry(player).Collection(p => p.Games).Load();
+ player.Games.Add(game);
+ context.Entry(game).Collection(g => g.PlayedBy).Load();
+ game.PlayedBy.Add(player);
+ }
+
+ context.Entry(player).Collection(p => p.TrackedTimes).Load();
+ player.TrackedTimes.Add(new (game, player, recentlyPlayedGame.playtime_forever));
+
+ try
+ {
+ context.SaveChanges();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+ }
+
+ private void TrackerLoop(object? obj)
+ {
+ if (obj is not IServiceProvider serviceProvider)
+ {
+ Log.Fatal("Tracker loop wasn't started.");
+ return;
+ }
+
+ while (_running)
+ {
+ IServiceScope scope = serviceProvider.CreateScope();
+ Context context = scope.ServiceProvider.GetRequiredService();
+ UpdatePlayers(context);
+ UpdateOwnedGames(context);
+ UpdateGameTimes(context);
+ scope.Dispose();
+ try
+ {
+ Thread.Sleep(Interval);
+ }
+ catch (ThreadInterruptedException)
+ {
+ Log.Debug("Thread interrupted");
+ }
+ catch (ThreadAbortException)
+ {
+ _running = false;
+ }
+ }
+ Log.Info("Thread exited");
+ }
+
+ public void ForceLoop()
+ {
+ _trackerThread.Interrupt();
+ }
+
+ public void Dispose()
+ {
+ _running = false;
+ _trackerThread.Interrupt();
+ }
+}
\ No newline at end of file
diff --git a/API/appsettings.json b/API/appsettings.json
index 993f2e4..3e3eb0a 100644
--- a/API/appsettings.json
+++ b/API/appsettings.json
@@ -2,5 +2,6 @@
"ConnectionStrings": {
"DefaultConnection": "Data Source=SteamGameTime.db;"
},
+ "UpdateInterval": "00:05:00",
"SteamWebApiKey": ""
}
diff --git a/Run/Program.cs b/Run/Program.cs
deleted file mode 100644
index 546589b..0000000
--- a/Run/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using API;
-
-namespace Run;
-
-class Program
-{
- static void Main(string[] args)
- {
- Api a = new (args);
- Tracker.Tracker t = new ();
- }
-}
\ No newline at end of file
diff --git a/Run/Run.csproj b/Run/Run.csproj
deleted file mode 100644
index 1c8f52c..0000000
--- a/Run/Run.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- Exe
- net9.0
- latest
- enable
- enable
-
-
-
-
-
-
-
-
diff --git a/SQLiteEF/Context.cs b/SQLiteEF/Context.cs
index d7aeff1..cb6f14d 100644
--- a/SQLiteEF/Context.cs
+++ b/SQLiteEF/Context.cs
@@ -1,16 +1,23 @@
+using log4net;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
namespace SQLiteEF;
-public class Context(IConfiguration configuration) : DbContext
+public class Context(DbContextOptions contextOptions) : DbContext(contextOptions)
{
public DbSet Players { get; set; }
public DbSet Games { get; set; }
+ private ILog Log { get; } = LogManager.GetLogger(typeof(Context));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
- optionsBuilder.UseSqlite(configuration.GetConnectionString("DefaultConnection"));
+ optionsBuilder.EnableDetailedErrors().EnableSensitiveDataLogging();
+ optionsBuilder.LogTo(s => Log.Debug(s), (_, level) => level <= LogLevel.Debug);
+ optionsBuilder.LogTo(s => Log.Info(s), (_, level) => level == LogLevel.Information);
+ optionsBuilder.LogTo(s => Log.Warn(s), (_, level) => level == LogLevel.Warning);
+ optionsBuilder.LogTo(s => Log.Error(s), (_, level) => level == LogLevel.Error);
+ optionsBuilder.LogTo(s => Log.Fatal(s), (_, level) => level >= LogLevel.Critical);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -20,14 +27,29 @@ public class Context(IConfiguration configuration) : DbContext
.WithMany(g => g.PlayedBy);
modelBuilder.Entity()
.Navigation(p => p.Games)
- .AutoInclude();
+ .AutoInclude(false);
+ modelBuilder.Entity()
+ .Navigation(p => p.TrackedTimes)
+ .AutoInclude(false);
+ modelBuilder.Entity()
+ .Navigation(g => g.PlayedBy)
+ .AutoInclude(false);
+ modelBuilder.Entity()
+ .Navigation(g => g.TrackedTimes)
+ .AutoInclude(false);
modelBuilder.Entity()
.HasOne(p => p.Player)
.WithMany(p => p.TrackedTimes)
.OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity()
+ .Navigation(t => t.Player)
+ .AutoInclude();
modelBuilder.Entity()
.HasOne(p => p.Game)
.WithMany(g => g.TrackedTimes)
.OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity()
+ .Navigation(t => t.Game)
+ .AutoInclude();
}
}
\ No newline at end of file
diff --git a/SQLiteEF/Game.cs b/SQLiteEF/Game.cs
index 04842c3..7c96587 100644
--- a/SQLiteEF/Game.cs
+++ b/SQLiteEF/Game.cs
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
namespace SQLiteEF;
@@ -7,6 +8,6 @@ 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!;
+ [JsonIgnore] public ICollection PlayedBy { get; init; } = null!;
+ [JsonIgnore] public ICollection TrackedTimes { get; init; } = null!;
}
\ No newline at end of file
diff --git a/SQLiteEF/Player.cs b/SQLiteEF/Player.cs
index db048f3..7786d5f 100644
--- a/SQLiteEF/Player.cs
+++ b/SQLiteEF/Player.cs
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
namespace SQLiteEF;
@@ -10,13 +11,13 @@ public class Player : IUpdateable
public string ProfileUrl { get; set; }
public string AvatarUrl { get; set; }
- public ICollection Games { get; init; } = null!;
- public ICollection TrackedTimes { get; init; } = null!;
- public DateTime UpdatedAt { get; set; } = DateTime.Now;
+ [JsonIgnore] public ICollection Games { get; init; } = null!;
+ [JsonIgnore] public ICollection TrackedTimes { get; init; } = null!;
+ public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
- public Player(ulong steamid, string name, string profileUrl, string avatarUrl)
+ public Player(ulong steamId, string name, string profileUrl, string avatarUrl)
{
- this.SteamId = steamid;
+ this.SteamId = steamId;
this.Name = name;
this.ProfileUrl = profileUrl;
this.AvatarUrl = avatarUrl;
@@ -27,9 +28,9 @@ public class Player : IUpdateable
///
/// EF CORE
///
- internal Player(ulong steamid, string name, string profileUrl, string avatarUrl, DateTime updatedAt)
+ internal Player(ulong steamId, string name, string profileUrl, string avatarUrl, DateTime updatedAt)
{
- this.SteamId = steamid;
+ this.SteamId = steamId;
this.Name = name;
this.ProfileUrl = profileUrl;
this.AvatarUrl = avatarUrl;
diff --git a/SQLiteEF/SQLiteEF.csproj b/SQLiteEF/SQLiteEF.csproj
index 1211f9b..f2fe46c 100644
--- a/SQLiteEF/SQLiteEF.csproj
+++ b/SQLiteEF/SQLiteEF.csproj
@@ -7,8 +7,14 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/SQLiteEF/TrackedTime.cs b/SQLiteEF/TrackedTime.cs
index c03095a..25f50b4 100644
--- a/SQLiteEF/TrackedTime.cs
+++ b/SQLiteEF/TrackedTime.cs
@@ -1,12 +1,13 @@
using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
namespace SQLiteEF;
[PrimaryKey("TimeStamp")]
public class TrackedTime
{
- public Game Game { get; init; }
- public Player Player { get; init; }
+ [JsonIgnore] public Game Game { get; init; }
+ [JsonIgnore] public Player Player { get; init; }
public DateTime TimeStamp { get; init; }
public ulong TimePlayed { get; init; }
@@ -14,7 +15,7 @@ public class TrackedTime
{
this.Game = game;
this.Player = player;
- this.TimeStamp = timeStamp??DateTime.Now;
+ this.TimeStamp = timeStamp??DateTime.UtcNow;
this.TimePlayed = timePlayed;
}
diff --git a/SteamApiWrapper/ReturnTypes/GetOwnedGames.cs b/SteamApiWrapper/ReturnTypes/GetOwnedGames.cs
deleted file mode 100644
index fa16ba6..0000000
--- a/SteamApiWrapper/ReturnTypes/GetOwnedGames.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-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
deleted file mode 100644
index 6686720..0000000
--- a/SteamApiWrapper/ReturnTypes/GetPlayerSummaries.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-namespace SteamApiWrapper.ReturnTypes;
-
-public record GetPlayerSummaries(Player[] players) : IReturnType;
\ No newline at end of file
diff --git a/SteamApiWrapper/SteamApiWrapper.cs b/SteamApiWrapper/SteamApiWrapper.cs
index 62c7fde..4220de4 100644
--- a/SteamApiWrapper/SteamApiWrapper.cs
+++ b/SteamApiWrapper/SteamApiWrapper.cs
@@ -1,3 +1,4 @@
+using log4net;
using Newtonsoft.Json.Linq;
using SteamApiWrapper.ReturnTypes;
@@ -5,9 +6,10 @@ namespace SteamApiWrapper;
public static class SteamApiWrapper
{
- public const string ApiUrl = "http://api.steampowered.com/";
+ public const string ApiUrl = "http://api.steampowered.com";
private static string _apiKey = string.Empty;
private static HttpClient client = new ();
+ private static ILog Log { get; } = LogManager.GetLogger(typeof(SteamApiWrapper));
public static void SetApiKey(string apiKey)
{
@@ -25,81 +27,93 @@ public static class SteamApiWrapper
return ret;
}
- public static GetOwnedGames GetOwnedGames(ulong steamid)
+ public static Game[] GetOwnedGames(ulong steamid)
{
+ Log.Debug("GetOwnedGames");
HttpRequestMessage request = BuildRequest(SupportedAPIInterface.IPlayerService, "GetOwnedGames", opts: new()
{
{"steamid", steamid.ToString()},
{"include_appinfo", "true"},
{"include_played_free_games", "true"}
});
+ Log.Debug(request.ToString());
try
{
HttpResponseMessage response = client.Send(request);
response.EnsureSuccessStatusCode();
JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
- return jObj["response"]?.ToObject()??new(0, []);
+ Log.Debug(jObj);
+ return jObj["response"]?["games"]?.ToObject()??[];
}
catch (Exception e)
{
- Console.WriteLine(e.Message);
- return new(0, []);
+ Log.Error(e);
+ return [];
}
}
- public static GetPlayerSummaries GetPlayerSummaries(ulong[] steamids)
+ public static Player[] GetPlayerSummaries(ulong[] steamids)
{
+ Log.Debug("GetPlayerSummaries");
HttpRequestMessage request = BuildRequest(SupportedAPIInterface.ISteamUser, "GetPlayerSummaries", "v0002", opts: new()
{
{"steamids", string.Join(',', steamids)}
});
+ Log.Debug(request.ToString());
try
{
HttpResponseMessage response = client.Send(request);
response.EnsureSuccessStatusCode();
JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
- return jObj["response"]?["players"]?.ToObject()??new([]);
+ Log.Debug(jObj);
+ return jObj["response"]?["players"]?.ToObject()??[];
}
catch (Exception e)
{
- Console.WriteLine(e.Message);
- return new([]);
+ Log.Error(e);
+ return [];
}
}
public static GetRecentlyPlayedGames GetRecentlyPlayedGames(ulong steamid)
{
+ Log.Debug("GetRecentlyPlayedGames");
HttpRequestMessage request = BuildRequest(SupportedAPIInterface.IPlayerService, "GetRecentlyPlayedGames", opts: new()
{
{"steamid", steamid.ToString()}
});
+ Log.Debug(request.ToString());
try
{
HttpResponseMessage response = client.Send(request);
response.EnsureSuccessStatusCode();
JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ Log.Debug(jObj);
return jObj["response"]?.ToObject()??new(0, []);
}
catch (Exception e)
{
- Console.WriteLine(e.Message);
+ Log.Error(e);
return new(0, []);
}
}
public static GetSupportedAPIList GetSupportedAPIList()
{
+ Log.Debug("GetSupportedAPIList");
HttpRequestMessage request = BuildRequest(SupportedAPIInterface.ISteamWebAPIUtil, "GetSupportedAPIList");
+ Log.Debug(request.ToString());
try
{
HttpResponseMessage response = client.Send(request);
response.EnsureSuccessStatusCode();
JObject jObj = JObject.Parse(response.Content.ReadAsStringAsync().Result);
+ Log.Debug(jObj);
return jObj["apilist"]?.ToObject()??new([]);
}
catch (Exception e)
{
- Console.WriteLine(e.Message);
+ Log.Error(e);
return new([]);
}
}
diff --git a/SteamApiWrapper/SteamApiWrapper.csproj b/SteamApiWrapper/SteamApiWrapper.csproj
index 728fd98..b4ddbdb 100644
--- a/SteamApiWrapper/SteamApiWrapper.csproj
+++ b/SteamApiWrapper/SteamApiWrapper.csproj
@@ -8,6 +8,7 @@
+
diff --git a/SteamGameTimeTrack.sln b/SteamGameTimeTrack.sln
index 0698c66..771bc57 100644
--- a/SteamGameTimeTrack.sln
+++ b/SteamGameTimeTrack.sln
@@ -4,10 +4,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{9
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
@@ -24,14 +20,6 @@ Global
{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
diff --git a/Tracker/Tracker.cs b/Tracker/Tracker.cs
deleted file mode 100644
index b399ad3..0000000
--- a/Tracker/Tracker.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using Microsoft.Extensions.Configuration;
-using SQLiteEF;
-using SteamApiWrapper.ReturnTypes;
-using SteamGame = SteamApiWrapper.ReturnTypes.Game;
-using Player = SQLiteEF.Player;
-using SteamPlayer = SteamApiWrapper.ReturnTypes.Player;
-using Steam = SteamApiWrapper.SteamApiWrapper;
-
-namespace Tracker;
-
-public class Tracker : IDisposable
-{
- private IConfiguration Configuration { get; init; }
- private readonly Thread _trackerThread;
- private bool _running = true;
-
- public Tracker()
- {
- Configuration = new ConfigurationBuilder()
- .AddJsonFile("appsettings.json")
- .Build();
- _trackerThread = new (TrackerLoop);
- _trackerThread.Start();
- }
-
- private void UpdatePlayers()
- {
- Context context = new (Configuration);
- IQueryable players = context.Players.Select(p => p.SteamId);
- GetPlayerSummaries summaries = Steam.GetPlayerSummaries(players.ToArray());
- foreach (SteamPlayer summariesPlayer in summaries.players)
- {
- if (context.Players.Find(summariesPlayer.steamid) is not { } player)
- {
- context.Players.Add(new (summariesPlayer.steamid, summariesPlayer.personaname,
- summariesPlayer.profileurl, summariesPlayer.avatar));
- continue;
- }
- player.Name = summariesPlayer.personaname;
- player.ProfileUrl = summariesPlayer.profileurl;
- player.AvatarUrl = summariesPlayer.avatar;
- try
- {
- context.SaveChanges();
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- }
- }
- }
-
- private void UpdateOwnedGames()
- {
- Context context = new (Configuration);
- IQueryable players = context.Players;
- foreach (Player player in players)
- {
- GetOwnedGames ownedGames = Steam.GetOwnedGames(player.SteamId);
- foreach (SteamGame ownedGame in ownedGames.games)
- {
- if (context.Games.Find(ownedGame.appid) is not { } game)
- {
- game = new(ownedGame.appid, ownedGame.name);
- }
-
- if (!player.Games.Contains(game))
- {
- player.Games.Add(game);
- game.PlayedBy.Add(player);
- player.TrackedTimes.Add(new (game, player, ownedGame.playtime_forever));
- }
-
- try
- {
- context.SaveChanges();
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- }
- }
- }
- }
-
- private void UpdateGametimes()
- {
- Context context = new (Configuration);
- IQueryable players = context.Players;
- foreach (Player player in players)
- {
- GetRecentlyPlayedGames recentlyPlayed = Steam.GetRecentlyPlayedGames(player.SteamId);
- foreach (SteamGame recentlyPlayedGame in recentlyPlayed.games)
- {
- if (context.Games.Find(recentlyPlayedGame.appid) is not { } game)
- {
- game = new(recentlyPlayedGame.appid, recentlyPlayedGame.name);
- }
-
- if (!player.Games.Contains(game))
- {
- player.Games.Add(game);
- game.PlayedBy.Add(player);
- }
-
- player.TrackedTimes.Add(new (game, player, recentlyPlayedGame.playtime_forever));
-
- try
- {
- context.SaveChanges();
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- }
- }
- }
- }
-
- private void TrackerLoop()
- {
- while (_running)
- {
- try
- {
- Thread.Sleep(TimeSpan.FromHours(1));
- }
- catch (ThreadInterruptedException)
- {
- Console.WriteLine("Thread interrupted");
- }
- catch (ThreadAbortException)
- {
- _running = false;
- }
- }
- Console.WriteLine("Thread exited");
- }
-
- public void ForceLoop()
- {
- _trackerThread.Interrupt();
- }
-
- static void Main(string[] args)
- {
- Tracker _ = new ();
- }
-
- public void Dispose()
- {
- _running = false;
- _trackerThread.Interrupt();
- }
-}
\ No newline at end of file
diff --git a/Tracker/Tracker.csproj b/Tracker/Tracker.csproj
deleted file mode 100644
index c46201b..0000000
--- a/Tracker/Tracker.csproj
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- Exe
- net9.0
- latest
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-