mirror of
https://github.com/RPCS3/discord-bot.git
synced 2024-11-27 04:00:34 +00:00
commit
92c24f9370
@ -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!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user