Game controller
This commit is contained in:
15
NostrStreamer/ApiModel/GameInfo.cs
Normal file
15
NostrStreamer/ApiModel/GameInfo.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NostrStreamer.ApiModel;
|
||||||
|
|
||||||
|
public class GameInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("cover")]
|
||||||
|
public string Cover { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("genres")]
|
||||||
|
public List<string> Genres { get; init; } = new();
|
||||||
|
}
|
@ -44,6 +44,14 @@ public class Config
|
|||||||
public string GeoIpDatabase { get; init; } = null!;
|
public string GeoIpDatabase { get; init; } = null!;
|
||||||
|
|
||||||
public List<EdgeLocation> Edges { get; init; } = new();
|
public List<EdgeLocation> Edges { get; init; } = new();
|
||||||
|
|
||||||
|
public TwitchApi Twitch { get; init; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TwitchApi
|
||||||
|
{
|
||||||
|
public string ClientId { get; init; } = null!;
|
||||||
|
public string ClientSecret { get; init; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LndConfig
|
public class LndConfig
|
||||||
|
35
NostrStreamer/Controllers/GameInfoController.cs
Normal file
35
NostrStreamer/Controllers/GameInfoController.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NostrStreamer.ApiModel;
|
||||||
|
using NostrStreamer.Services;
|
||||||
|
|
||||||
|
namespace NostrStreamer.Controllers;
|
||||||
|
|
||||||
|
[Route("/api/v1/games")]
|
||||||
|
public class GameInfoController : Controller
|
||||||
|
{
|
||||||
|
private readonly GameDb _gameDb;
|
||||||
|
|
||||||
|
public GameInfoController(GameDb gameDb)
|
||||||
|
{
|
||||||
|
_gameDb = gameDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetGames([FromQuery] string q, [FromQuery]int limit = 10)
|
||||||
|
{
|
||||||
|
var data = await _gameDb.SearchGames(q);
|
||||||
|
|
||||||
|
var mapped = data?.Select(a => new GameInfo()
|
||||||
|
{
|
||||||
|
Name = a.Name,
|
||||||
|
Cover = $"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/{a.Cover?.ImageId}.jpg",
|
||||||
|
Genres = a.Genres.Select(b => b.Name).ToList()
|
||||||
|
});
|
||||||
|
|
||||||
|
return Json(mapped, new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="proto/*.proto" GrpcServices="Client" ProtoRoot="proto" />
|
<Protobuf Include="proto/*.proto" GrpcServices="Client" ProtoRoot="proto" />
|
||||||
|
<Protobuf Include="proto2/games.proto" ProtoRoot="proto2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\.dockerignore">
|
<Content Include="..\.dockerignore">
|
||||||
|
@ -74,7 +74,10 @@ internal static class Program
|
|||||||
// lnd services
|
// lnd services
|
||||||
services.AddSingleton<LndNode>();
|
services.AddSingleton<LndNode>();
|
||||||
services.AddHostedService<LndInvoicesStream>();
|
services.AddHostedService<LndInvoicesStream>();
|
||||||
|
|
||||||
|
// game services
|
||||||
|
services.AddSingleton<GameDb>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
|
95
NostrStreamer/Services/GameDb.cs
Normal file
95
NostrStreamer/Services/GameDb.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using Igdb;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NostrStreamer.Services;
|
||||||
|
|
||||||
|
public class GameDb
|
||||||
|
{
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private readonly ILogger<GameDb> _logger;
|
||||||
|
private readonly TwitchApi _config;
|
||||||
|
private Token? _currentToken = null;
|
||||||
|
private readonly SemaphoreSlim _tokenLock = new(1, 1);
|
||||||
|
|
||||||
|
public GameDb(HttpClient client, ILogger<GameDb> logger, Config config)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_logger = logger;
|
||||||
|
_config = config.Twitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshToken()
|
||||||
|
{
|
||||||
|
bool NeedsRefresh() => _currentToken == null || _currentToken.Loaded.AddSeconds(_currentToken.ExpiresIn) < DateTime.UtcNow;
|
||||||
|
if (NeedsRefresh())
|
||||||
|
{
|
||||||
|
await _tokenLock.WaitAsync();
|
||||||
|
if (!NeedsRefresh()) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url =
|
||||||
|
$"https://id.twitch.tv/oauth2/token?client_id={_config.ClientId}&client_secret={_config.ClientSecret}&grant_type=client_credentials";
|
||||||
|
|
||||||
|
var rsp = await _client.PostAsync(url, null);
|
||||||
|
if (rsp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var newToken = JsonConvert.DeserializeObject<Token>(await rsp.Content.ReadAsStringAsync());
|
||||||
|
if (newToken != default)
|
||||||
|
{
|
||||||
|
_currentToken = newToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to refresh token {msg}", ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_tokenLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Game>?> SearchGames(string s, int limit = 10)
|
||||||
|
{
|
||||||
|
await RefreshToken();
|
||||||
|
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Post, "https://api.igdb.com/v4/games.pb");
|
||||||
|
req.Headers.Add("Client-ID", _config.ClientId);
|
||||||
|
req.Headers.Authorization = new("Bearer", _currentToken?.AccessToken);
|
||||||
|
req.Content = new StringContent($"search \"{s}\"; fields id,cover.image_id,genres.name,name; limit {limit};");
|
||||||
|
|
||||||
|
var rsp = await _client.SendAsync(req);
|
||||||
|
if (rsp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var rspStream = await rsp.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
var ret = GameResult.Parser.ParseFrom(rspStream);
|
||||||
|
return ret.Games.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var content = await rsp.Content.ReadAsStringAsync();
|
||||||
|
_logger.LogWarning("Failed to fetch games {msg}", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Token
|
||||||
|
{
|
||||||
|
[JsonProperty("access_token")]
|
||||||
|
public string AccessToken { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("expires_in")]
|
||||||
|
public int ExpiresIn { get; init; }
|
||||||
|
|
||||||
|
[JsonProperty("token_type")]
|
||||||
|
public string TokenType { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public DateTime Loaded { get; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,10 @@
|
|||||||
"Longitude": 2.12664,
|
"Longitude": 2.12664,
|
||||||
"Latitude": 50.98515
|
"Latitude": 50.98515
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"Twitch": {
|
||||||
|
"ClientId": "123",
|
||||||
|
"ClientSecret": "aaa"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1235
NostrStreamer/proto2/games.proto
Normal file
1235
NostrStreamer/proto2/games.proto
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user