Merge pull request #961 from FlexBy420/patch-3
Some checks failed
.NET / Run Tests (push) Has been cancelled
.NET / Build Release (push) Has been cancelled
.NET / ConfigTest (push) Has been cancelled
.NET / Build Docker image (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled

Update ird command
This commit is contained in:
Haxy 2024-09-30 15:07:50 +01:00 committed by GitHub
commit 92c24f9370
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 123 additions and 211 deletions

View File

@ -1,215 +1,129 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CompatApiClient; using CompatApiClient;
using CompatApiClient.Compression; using CompatApiClient.Compression;
using CompatApiClient.Formatters;
using CompatApiClient.Utils;
using HtmlAgilityPack;
using IrdLibraryClient.IrdFormat; using IrdLibraryClient.IrdFormat;
using IrdLibraryClient.POCOs; using IrdLibraryClient.POCOs;
namespace IrdLibraryClient; namespace IrdLibraryClient
public class IrdClient
{ {
public static readonly string BaseUrl = "https://ps3.aldostools.org"; public class IrdClient
private readonly HttpClient client;
private readonly JsonSerializerOptions jsonOptions;
public IrdClient()
{ {
client = HttpClientFactory.Create(new CompressionMessageHandler()); public static readonly string JsonUrl = "https://flexby420.github.io/playstation_3_ird_database/all.json";
jsonOptions = new() private readonly HttpClient client;
{ private readonly JsonSerializerOptions jsonOptions;
PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase, private static readonly string BaseDownloadUri = "https://github.com/FlexBy420/playstation_3_ird_database/raw/main/";
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
IncludeFields = true,
};
}
public static string GetDownloadLink(string irdFilename) => $"{BaseUrl}/ird/{irdFilename}"; public IrdClient()
public async Task<SearchResult?> SearchAsync(string query, CancellationToken cancellationToken)
{
query = query.ToUpper();
try
{ {
var requestUri = new Uri(BaseUrl + "/ird.html"); client = HttpClientFactory.Create(new CompressionMessageHandler());
using var getMessage = new HttpRequestMessage(HttpMethod.Get, requestUri); jsonOptions = new JsonSerializerOptions
using var response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false); {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
IncludeFields = true,
};
}
public async Task<List<IrdInfo>> SearchAsync(string query, CancellationToken cancellationToken)
{
query = query.ToUpper();
try try
{ {
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false); using var response = await client.GetAsync(JsonUrl, cancellationToken).ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); if (!response.IsSuccessStatusCode)
HtmlDocument doc = new();
doc.LoadHtml(result);
return new()
{ {
Data = doc.DocumentNode.Descendants("tr") ApiConfig.Log.Error($"Failed to fetch IRD data: {response.StatusCode}");
.Skip(1) return new List<IrdInfo>();
.Select(tr => tr.Elements("td").ToList())
.Where(tds => tds.Count > 1 && tds[0].InnerText == query)
.Select(tds =>
{
var i = tds.Select(td => td.InnerText.Trim()).ToArray();
return new SearchResultItem
{
Id = i[0],
Title = i[1],
GameVersion = i[2],
UpdateVersion = i[3],
Size = i[4],
FileCount = i[5],
FolderCount = i[6],
MD5 = i[7],
IrdName = i[8],
Filename = i[0] + "-" + i[8] + ".ird",
};
})
.ToList(),
};
}
catch (Exception e)
{
ConsoleLogger.PrintError(e, response);
return null;
}
}
catch (Exception e)
{
ApiConfig.Log.Error(e);
return null;
}
}
public async Task<List<Ird>> DownloadAsync(string productCode, string localCachePath, CancellationToken cancellationToken)
{
var result = new List<Ird>();
try
{
// first we search local cache and try to load whatever data we can
var localCacheItems = new List<string>();
try
{
var tmpCacheItemList = Directory.GetFiles(localCachePath, productCode + "*.ird", SearchOption.TopDirectoryOnly)
.Select(Path.GetFileName)
.ToList();
foreach (var item in tmpCacheItemList)
{
if (string.IsNullOrEmpty(item))
continue;
try
{
result.Add(IrdParser.Parse(await File.ReadAllBytesAsync(Path.Combine(localCachePath, item), cancellationToken).ConfigureAwait(false)));
localCacheItems.Add(item);
}
catch (Exception ex)
{
ApiConfig.Log.Warn(ex, "Error reading local IRD file: " + ex.Message);
}
} }
}
catch (Exception e)
{
ApiConfig.Log.Warn(e, "Error accessing local IRD cache: " + e.Message);
}
ApiConfig.Log.Debug($"Found {localCacheItems.Count} cached items for {productCode}");
SearchResult? searchResult = null;
// then try to do IRD Library search var jsonResult = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
try var irdData = JsonSerializer.Deserialize<Dictionary<string, List<IrdInfo>>>(jsonResult, jsonOptions);
{ if (irdData == null)
searchResult = await SearchAsync(productCode, cancellationToken).ConfigureAwait(false); {
ApiConfig.Log.Error("Failed to deserialize IRD JSON data.");
return new List<IrdInfo>();
}
if (irdData.TryGetValue(query, out var items))
{
return items;
}
return new List<IrdInfo>();
} }
catch (Exception e) catch (Exception e)
{ {
ApiConfig.Log.Error(e); ApiConfig.Log.Error(e);
return new List<IrdInfo>();
} }
var tmpFilesToGet = searchResult?.Data?
.Select(i => i.Filename)
.Except(localCacheItems, StringComparer.InvariantCultureIgnoreCase)
.ToList();
if (tmpFilesToGet is null or {Count: 0})
return result;
// as IRD Library could return more data than we found, try to check for all the items locally
var filesToDownload = new List<string>();
foreach (var item in tmpFilesToGet)
{
if (string.IsNullOrEmpty(item))
continue;
try
{
var localItemPath = Path.Combine(localCachePath, item);
if (File.Exists(localItemPath))
{
result.Add(IrdParser.Parse(await File.ReadAllBytesAsync(localItemPath, cancellationToken).ConfigureAwait(false)));
localCacheItems.Add(item);
}
else
filesToDownload.Add(item);
}
catch (Exception ex)
{
ApiConfig.Log.Warn(ex, "Error reading local IRD file: " + ex.Message);
filesToDownload.Add(item);
}
}
ApiConfig.Log.Debug($"Found {tmpFilesToGet.Count} total matches for {productCode}, {result.Count} already cached");
if (filesToDownload.Count == 0)
return result;
// download the remaining .ird files
foreach (var item in filesToDownload)
{
try
{
var resultBytes = await client.GetByteArrayAsync(GetDownloadLink(item), cancellationToken).ConfigureAwait(false);
result.Add(IrdParser.Parse(resultBytes));
try
{
await File.WriteAllBytesAsync(Path.Combine(localCachePath, item), resultBytes, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
ApiConfig.Log.Warn(ex, $"Failed to write {item} to local cache: {ex.Message}");
}
}
catch (Exception e)
{
ApiConfig.Log.Warn(e, $"Failed to download {item}: {e.Message}");
}
}
ApiConfig.Log.Debug($"Returning {result.Count} .ird files for {productCode}");
return result;
} }
catch (Exception e)
public async Task<List<Ird>> DownloadAsync(string productCode, string localCachePath, CancellationToken cancellationToken)
{ {
ApiConfig.Log.Error(e); var result = new List<Ird>();
return result; try
{
var searchResults = await SearchAsync(productCode, cancellationToken).ConfigureAwait(false);
if (searchResults == null || !searchResults.Any())
{
ApiConfig.Log.Debug($"No IRD files found for {productCode}");
return result;
}
foreach (var item in searchResults)
{
var localFilePath = Path.Combine(localCachePath, $"{productCode}-{item.Link.Split('/').Last()}.ird");
if (!File.Exists(localFilePath))
{
try
{
var downloadLink = GetDownloadLink(item.Link);
var fileBytes = await client.GetByteArrayAsync(downloadLink, cancellationToken).ConfigureAwait(false);
await File.WriteAllBytesAsync(localFilePath, fileBytes, cancellationToken).ConfigureAwait(false);
result.Add(IrdParser.Parse(fileBytes));
}
catch (Exception ex)
{
ApiConfig.Log.Warn(ex, $"Failed to download {item.Link}: {ex.Message}");
}
}
}
ApiConfig.Log.Debug($"Returning {result.Count} .ird files for {productCode}");
return result;
}
catch (Exception e)
{
ApiConfig.Log.Error(e);
return result;
}
}
public static string GetDownloadLink(string relativeLink)
{
var fullUrl = new Uri(new Uri(BaseDownloadUri), relativeLink);
return Uri.EscapeUriString(fullUrl.ToString());
} }
} }
private static string? GetTitle(string? html) public class IrdInfo
{ {
if (string.IsNullOrEmpty(html)) [JsonPropertyName("title")]
return null; public string Title { get; set; } = null!;
[JsonPropertyName("fw-ver")]
var idx = html.LastIndexOf("</span>", StringComparison.Ordinal); public string? FwVer { get; set; }
var result = html[(idx + 7)..].Trim(); [JsonPropertyName("game-ver")]
if (result is {Length: >0}) public string? GameVer { get; set; }
return result; [JsonPropertyName("app-ver")]
return null; public string? AppVer { get; set; }
[JsonPropertyName("link")]
public string Link { get; set; } = null!;
} }
} }

View File

@ -1,39 +1,37 @@
using CompatApiClient.Utils; using CompatApiClient.Utils;
using DSharpPlus.Entities; using DSharpPlus.Entities;
using IrdLibraryClient; using IrdLibraryClient;
using IrdLibraryClient.POCOs; using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CompatBot.Utils.ResultFormatters; namespace CompatBot.Utils.ResultFormatters
public static class IrdSearchResultFormatter
{ {
public static DiscordEmbedBuilder AsEmbed(this SearchResult? searchResult) public static class IrdSearchResultFormatter
{ {
var result = new DiscordEmbedBuilder public static DiscordEmbedBuilder AsEmbed(this List<IrdInfo> irdInfos)
{ {
//Title = "IRD Library Search Result", var result = new DiscordEmbedBuilder
Color = Config.Colors.DownloadLinks, {
}; // Title = "IRD Library Search Result",
if (searchResult?.Data is null or {Count: 0}) Color = Config.Colors.DownloadLinks,
{ };
result.Color = Config.Colors.LogResultFailed; if (irdInfos == null || !irdInfos.Any())
result.Description = "No matches were found"; {
result.Color = Config.Colors.LogResultFailed;
result.Description = "No matches were found";
return result;
}
foreach (var item in irdInfos)
{
if (string.IsNullOrEmpty(item.Link))
continue;
result.AddField(
$"{item.Title} [v{item.GameVer} FW {item.FwVer}]",
$"[⏬ {Path.GetFileName(item.Link)}]({IrdClient.GetDownloadLink(item.Link)})"
);
}
return result; return result;
} }
foreach (var item in searchResult.Data)
{
if (string.IsNullOrEmpty(item.Filename))
continue;
string[] parts = item.Filename.Split('-');
if (parts.Length == 1)
parts = ["", item.Filename];
result.AddField(
$"[{parts[0]} v{item.GameVersion}] {item.Title?.Sanitize().Trim(EmbedPager.MaxFieldTitleLength)}",
$"[⏬ `{parts[1].Sanitize().Trim(200)}`]({IrdClient.GetDownloadLink(item.Filename)})"
);
}
return result;
} }
} }