mirror of
https://github.com/RPCS3/discord-bot.git
synced 2026-01-31 01:25:22 +01:00
Merge pull request #1006 from 13xforever/vnext
Update game updates result formatting with discord components v2
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageReference Include="NLog" Version="6.0.2" />
|
||||
<PackageReference Include="NLog" Version="6.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -34,28 +34,32 @@ internal static partial class Psn
|
||||
}
|
||||
|
||||
await ctx.DeferResponseAsync(ephemeral).ConfigureAwait(false);
|
||||
List<DiscordEmbedBuilder> embeds;
|
||||
List<DiscordMessageBuilder> msgList;
|
||||
try
|
||||
{
|
||||
var updateInfo = await TitleUpdateInfoProvider.GetAsync(id, Config.Cts.Token).ConfigureAwait(false);
|
||||
embeds = await updateInfo.AsEmbedAsync(ctx.Client, id).ConfigureAwait(false);
|
||||
msgList = await updateInfo.AsMessageAsync(ctx.Client, id).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Config.Log.Warn(e, "Failed to get title update info");
|
||||
embeds =
|
||||
msgList =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Color = Config.Colors.Maintenance,
|
||||
Title = "Service is unavailable",
|
||||
Description = "There was an error communicating with the service. Try again in a few minutes.",
|
||||
}
|
||||
new DiscordMessageBuilder()
|
||||
.EnableV2Components()
|
||||
.AddContainerComponent(
|
||||
new([new DiscordTextDisplayComponent(
|
||||
$"""
|
||||
### Service is unavailable
|
||||
There was an error communicating with the service. Try again in a few minutes.
|
||||
"""
|
||||
)], color: Config.Colors.Maintenance)
|
||||
)
|
||||
];
|
||||
}
|
||||
await ctx.RespondAsync(embeds[0], ephemeral: ephemeral).ConfigureAwait(false);
|
||||
foreach (var embed in embeds.Skip(1).Take(EmbedPager.MaxFollowupMessages))
|
||||
await ctx.FollowupAsync(embed, ephemeral: ephemeral).ConfigureAwait(false);
|
||||
await ctx.RespondAsync(new DiscordInteractionResponseBuilder(msgList[0]).AsEphemeral(ephemeral: ephemeral)).ConfigureAwait(false);
|
||||
foreach (var msg in msgList.Skip(1).Take(EmbedPager.MaxFollowupMessages))
|
||||
await ctx.FollowupAsync(new DiscordInteractionResponseBuilder(msg).AsEphemeral(ephemeral: ephemeral)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("firmware")]
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
||||
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.4.0" />
|
||||
<PackageReference Include="DSharpPlus" Version="5.0.0-nightly-02536" />
|
||||
<PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02536" />
|
||||
<PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02536" />
|
||||
<PackageReference Include="DSharpPlus" Version="5.0.0-nightly-02541" />
|
||||
<PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02541" />
|
||||
<PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02541" />
|
||||
<PackageReference Include="DSharpPlus.Natives.Zstd" Version="1.5.7.21" />
|
||||
<PackageReference Include="Florence2" Version="25.7.59767" />
|
||||
<PackageReference Include="Google.Apis.Drive.v3" Version="1.70.0.3856" />
|
||||
@@ -69,8 +69,8 @@
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
|
||||
<PackageReference Include="Nerdbank.Streams" Version="2.12.90" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="NLog" Version="6.0.2" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="6.0.2" />
|
||||
<PackageReference Include="NLog" Version="6.0.3" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="6.0.3" />
|
||||
<PackageReference Include="NReco.Text.AhoCorasickDoubleArrayTrie" Version="1.1.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.40.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
|
||||
@@ -43,6 +43,7 @@ internal partial class LogParser
|
||||
["SYS: Path"] = BootPathSys(),
|
||||
["LDR: Path:"] = BootPathDigitalLdr(),
|
||||
["SYS: Path:"] = BootPathDigitalSys(),
|
||||
["SYS: Booting savestate"] = BootingFromSavestate(),
|
||||
["custom config:"] = CustomConfigPath(),
|
||||
["patch_log: Failed to load patch file"] = FailedPatchPath(),
|
||||
["Undisputed"] = UfcModFlag(),
|
||||
@@ -50,7 +51,6 @@ internal partial class LogParser
|
||||
},
|
||||
EndTrigger = ["Used configuration:"],
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Extractors = new()
|
||||
@@ -90,7 +90,6 @@ internal partial class LogParser
|
||||
},
|
||||
EndTrigger = ["VFS:"],
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Extractors = new()
|
||||
@@ -99,7 +98,6 @@ internal partial class LogParser
|
||||
},
|
||||
EndTrigger = ["Video:"],
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Extractors = new()
|
||||
@@ -140,7 +138,6 @@ internal partial class LogParser
|
||||
},
|
||||
EndTrigger = ["Audio:"],
|
||||
},
|
||||
|
||||
new() // Audio, Input/Output, System, Net, Miscellaneous
|
||||
{
|
||||
Extractors = new()
|
||||
@@ -154,6 +151,10 @@ internal partial class LogParser
|
||||
|
||||
["Pad:"] = GamepadType(),
|
||||
|
||||
["Start Paused:"] = StartPausedSavestate(),
|
||||
["Suspend Emulation Savestate Mode:"] = SuspendEmulationSavestate(),
|
||||
["Compatible Savestate Mode:"] = CompatibleSavestate(),
|
||||
|
||||
["Automatically start games after boot:"] = AutoStartAfterBoot(),
|
||||
["Always start after boot:"] = AlwaysStartAfterBoot(),
|
||||
["Use native user interface:"] = NativeUIMode(),
|
||||
@@ -161,7 +162,6 @@ internal partial class LogParser
|
||||
},
|
||||
EndTrigger = ["Log:"],
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Extractors = new()
|
||||
@@ -171,7 +171,6 @@ internal partial class LogParser
|
||||
EndTrigger = ["·"],
|
||||
OnSectionEnd = MarkAsComplete,
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Extractors = new()
|
||||
|
||||
@@ -208,6 +208,14 @@ internal partial class LogParser
|
||||
private static partial Regex AudioTimeStretching();
|
||||
[GeneratedRegex("Pad: (?<pad_handler>[^\r\n]*?)\r?$", DefaultOptions)]
|
||||
private static partial Regex GamepadType();
|
||||
|
||||
[GeneratedRegex("Start Paused: (?<start_paused_savestate>[^\r\n]*?)\r?$", DefaultOptions)]
|
||||
private static partial Regex StartPausedSavestate();
|
||||
[GeneratedRegex("Suspend Emulation Savestate Mode: (?<suspend_emulation_savestate>[^\r\n]*?)\r?$", DefaultOptions)]
|
||||
private static partial Regex SuspendEmulationSavestate();
|
||||
[GeneratedRegex("Compatible Savestate Mode: (?<compatible_savestate>[^\r\n]*?)\r?$", DefaultOptions)]
|
||||
private static partial Regex CompatibleSavestate();
|
||||
|
||||
[GeneratedRegex("Automatically start games after boot: (?<auto_start_on_boot>[^\r\n]*?)\r?$", DefaultOptions)]
|
||||
private static partial Regex AutoStartAfterBoot();
|
||||
[GeneratedRegex("Always start after boot: (?<always_start_on_boot>[^\r\n]*?)\r?$", DefaultOptions)]
|
||||
@@ -370,4 +378,6 @@ internal partial class LogParser
|
||||
DefaultOptions
|
||||
)]
|
||||
private static partial Regex SaveDataBeforeSegfault();
|
||||
[GeneratedRegex(@"Booting savestate from gamelist per (?<booting_savestate>.+)...\r?$", DefaultOptions)]
|
||||
private static partial Regex BootingFromSavestate();
|
||||
}
|
||||
@@ -227,7 +227,7 @@ internal static partial class LogParserResult
|
||||
{
|
||||
if (colA.lines?.Count > 0 && colB.lines?.Count > 0)
|
||||
{
|
||||
var isCustomSettings = items["custom_config"] != null;
|
||||
var isCustomSettings = items["custom_config"] is EnabledMark;
|
||||
var colAToRemove = colA.lines.Count(l => l.EndsWith("N/A"));
|
||||
var colBToRemove = colB.lines.Count(l => l.EndsWith("N/A"));
|
||||
var linesToRemove = Math.Min(colAToRemove, colBToRemove);
|
||||
|
||||
@@ -420,11 +420,11 @@ internal static partial class LogParserResult
|
||||
discAsPkg |= items["ldr_game_serial"] is string ldrGameSerial && ldrGameSerial.StartsWith("NP", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
discAsPkg |= category == "HG" && !(items["serial"]?.StartsWith("NP", StringComparison.InvariantCultureIgnoreCase) ?? false);
|
||||
discAsPkg |= category is "HG" && !(items["serial"]?.StartsWith("NP", StringComparison.InvariantCultureIgnoreCase) ?? false);
|
||||
if (discInsideGame)
|
||||
notes.Add($"❌ Disc game inside `{items["ldr_disc"]}`");
|
||||
if (discAsPkg)
|
||||
notes.Add($"ℹ️ Disc game installed as a PKG ");
|
||||
notes.Add("ℹ️ Disc game installed as a PKG ");
|
||||
|
||||
if (!string.IsNullOrEmpty(items["native_ui_input"]))
|
||||
notes.Add("⚠️ Pad initialization problem detected; try disabling `Native UI`");
|
||||
@@ -433,10 +433,13 @@ internal static partial class LogParserResult
|
||||
else if (items["audio_backend_init_error"] is string audioBackend)
|
||||
notes.Add($"⚠️ {audioBackend} initialization failed; make sure you have a working audio output device");
|
||||
|
||||
if (!string.IsNullOrEmpty(items["fw_missing_msg"])
|
||||
|| !string.IsNullOrEmpty(items["fw_missing_something"]))
|
||||
if (items["fw_missing_msg"] is not {Length: >0}
|
||||
|| items["fw_missing_something"] is not {Length: >0})
|
||||
notes.Add("❌ PS3 firmware is missing or corrupted");
|
||||
|
||||
if (items["booting_savestate"] is EnabledMark)
|
||||
notes.Add("ℹ️ Game was booted from a save state");
|
||||
|
||||
if (multiItems["game_mod"] is { Length: >0 } mods)
|
||||
{
|
||||
var mod = mods[0];
|
||||
|
||||
@@ -552,12 +552,22 @@ internal static partial class LogParserResult
|
||||
notes.Add($"⚠️ Please change `SPU Block Size` to `Safe/Mega`, currently `{spuBlockSize}` is unstable.");
|
||||
}
|
||||
|
||||
if (items["auto_start_on_boot"] == DisabledMark)
|
||||
notes.Add("❓ `Automatically start games after boot` is disabled");
|
||||
else if (items["always_start_on_boot"] == DisabledMark)
|
||||
notes.Add("❓ `Always start after boot` is disabled");
|
||||
if (items["booting_savestate"] is DisabledMark)
|
||||
{
|
||||
if (items["auto_start_on_boot"] is DisabledMark)
|
||||
notes.Add("❓ `Automatically start games after boot` is disabled");
|
||||
else if (items["always_start_on_boot"] is DisabledMark)
|
||||
notes.Add("❓ `Always start after boot` is disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items["start_paused_savestate"] is EnabledMark)
|
||||
notes.Add("❓ `Pause emulation after loading savestates` is disabled");
|
||||
if (items["compatible_savestate"] is not EnabledMark)
|
||||
notes.Add("ℹ️ If you have weird compatibility issues after boot, try enabling `SPU-Compatible Savestate Mode`");
|
||||
}
|
||||
|
||||
if (items["custom_config"] != null && notes.Any())
|
||||
if (items["custom_config"] is EnabledMark && notes.Count > 0)
|
||||
generalNotes.Add("⚠️ To change custom configuration, **Right-click on the game**, then `Configure`");
|
||||
|
||||
var notesContent = new StringBuilder();
|
||||
|
||||
@@ -745,6 +745,9 @@ internal static partial class LogParserResult
|
||||
if (items["game_update_version"] is string gameUpVer && gameUpVer.StartsWith("0"))
|
||||
items["game_update_version"] = gameUpVer[1..];
|
||||
|
||||
items["custom_config"] = items["custom_config"] is { Length: > 0 } ? EnabledMark : DisabledMark;
|
||||
items["booting_savestate"] = items["booting_savestate"] is {Length: >0} ? EnabledMark : DisabledMark;
|
||||
|
||||
if (multiItems["fatal_error"] is UniqueList<string> {Count: > 0} fatalErrors)
|
||||
multiItems["fatal_error"] = new(fatalErrors.Select(str => str.Contains("'tex00'") ? str.Split('\n', 2)[0] : str), fatalErrors.Comparer);
|
||||
|
||||
|
||||
@@ -9,65 +9,104 @@ namespace CompatBot.Utils.ResultFormatters;
|
||||
internal static class TitlePatchFormatter
|
||||
{
|
||||
// thanks BCES00569
|
||||
public static async Task<List<DiscordEmbedBuilder>> AsEmbedAsync(this TitlePatch? patch, DiscordClient client, string productCode)
|
||||
public static async Task<List<DiscordMessageBuilder>> AsMessageAsync(this TitlePatch? patch, DiscordClient client, string productCode)
|
||||
{
|
||||
var result = new List<DiscordEmbedBuilder>();
|
||||
var pkgs = patch?.Tag?.Packages;
|
||||
var title = pkgs?.Select(p => p.ParamSfo?.Title).LastOrDefault(t => !string.IsNullOrEmpty(t))
|
||||
?? await ThumbnailProvider.GetTitleNameAsync(productCode, Config.Cts.Token).ConfigureAwait(false)
|
||||
?? productCode;
|
||||
var content = new StringBuilder();
|
||||
var thumbnailUrl = await client.GetThumbnailUrlAsync(productCode).ConfigureAwait(false);
|
||||
var embedBuilder = new DiscordEmbedBuilder
|
||||
if (pkgs is {Length: >0})
|
||||
{
|
||||
Title = title,
|
||||
Color = Config.Colors.DownloadLinks,
|
||||
}.WithThumbnail(thumbnailUrl);
|
||||
if (pkgs?.Length > 1)
|
||||
{
|
||||
var pages = pkgs.Length / EmbedPager.MaxFields + (pkgs.Length % EmbedPager.MaxFields == 0 ? 0 : 1);
|
||||
if (pages > 1)
|
||||
embedBuilder.Title = $"{title} [Part 1 of {pages}]".Trim(EmbedPager.MaxFieldTitleLength);
|
||||
embedBuilder.Description = $"""
|
||||
ℹ️ Total download size of all {pkgs.Length} packages is {pkgs.Sum(p => p.Size).AsStorageUnit()}.
|
||||
⏩ You can use tools such as [rusty-psn](https://github.com/RainbowCookie32/rusty-psn/releases/latest) or [PySN](https://github.com/AphelionWasTaken/PySN/releases/latest) for mass download of all updates.
|
||||
content.AppendLine($"### {title}");
|
||||
if (pkgs.Length > 1)
|
||||
content.AppendLine(
|
||||
$"""
|
||||
ℹ️ Total download size of all {pkgs.Length} packages is {pkgs.Sum(p => p.Size).AsStorageUnit()}.
|
||||
⏩ You can use tools such as [rusty-psn](https://github.com/RainbowCookie32/rusty-psn/releases/latest) or [PySN](https://github.com/AphelionWasTaken/PySN/releases/latest) for mass download of all updates.
|
||||
|
||||
⚠️ You **must** install listed updates in order, starting with the first one. You **can not** skip intermediate versions.
|
||||
""";
|
||||
var i = 0;
|
||||
do
|
||||
{
|
||||
var pkg = pkgs[i++];
|
||||
embedBuilder.AddField($"Update v{pkg.Version} ({pkg.Size.AsStorageUnit()})", $"[⏬ {GetLinkName(pkg.Url)}]({pkg.Url})");
|
||||
if (i % EmbedPager.MaxFields == 0)
|
||||
{
|
||||
result.Add(embedBuilder);
|
||||
embedBuilder = new DiscordEmbedBuilder
|
||||
{
|
||||
Title = $"{title} [Part {i / EmbedPager.MaxFields + 1} of {pages}]".Trim(EmbedPager.MaxFieldTitleLength),
|
||||
Color = Config.Colors.DownloadLinks,
|
||||
}.WithThumbnail(thumbnailUrl);
|
||||
}
|
||||
} while (i < pkgs.Length);
|
||||
⚠️ You **must** install listed updates in order, starting with the first one. You **can not** skip intermediate versions.
|
||||
"""
|
||||
).AppendLine();
|
||||
foreach (var pkg in pkgs)
|
||||
content.AppendLine($"""[⏬ Update v`{pkg.Version}` ({pkg.Size.AsStorageUnit()})]({pkg.Url})""");
|
||||
}
|
||||
else if (pkgs?.Length == 1)
|
||||
else if (pkgs is [var pkg])
|
||||
{
|
||||
embedBuilder.Title = $"{title} update v{pkgs[0].Version} ({pkgs[0].Size.AsStorageUnit()})";
|
||||
embedBuilder.Description = $"[⏬ {Path.GetFileName(GetLinkName(pkgs[0].Url))}]({pkgs[0].Url})";
|
||||
content.AppendLine(
|
||||
$"""
|
||||
### {title} update v{pkg.Version} ({pkg.Size.AsStorageUnit()})
|
||||
[⏬ {Path.GetFileName(GetLinkName(pkg.Url))}]({pkg.Url})
|
||||
"""
|
||||
);
|
||||
}
|
||||
else if (patch != null)
|
||||
embedBuilder.Description = "No updates available";
|
||||
else
|
||||
embedBuilder.Description = "No update information available";
|
||||
if (!result.Any() || embedBuilder.Fields.Any())
|
||||
result.Add(embedBuilder);
|
||||
content.AppendLine($"### {title}")
|
||||
.AppendLine("No updates available");
|
||||
if (patch?.OfflineCacheTimestamp is DateTime cacheTimestamp)
|
||||
result[^1].WithFooter($"Offline cache, last updated {(DateTime.UtcNow - cacheTimestamp).AsTimeDeltaDescription()} ago");
|
||||
content.AppendLine()
|
||||
.AppendLine($"-# Offline cache, last updated {(DateTime.UtcNow - cacheTimestamp).AsTimeDeltaDescription()} ago");
|
||||
|
||||
var result = new List<DiscordMessageBuilder>();
|
||||
IReadOnlyList<DiscordTextDisplayComponent> contentParts = Split(content);
|
||||
foreach (var page in contentParts)
|
||||
{
|
||||
IReadOnlyList<DiscordComponent> msgBody;
|
||||
if (thumbnailUrl is { Length: > 0 })
|
||||
msgBody =
|
||||
[
|
||||
new DiscordSectionComponent([page], new DiscordThumbnailComponent(thumbnailUrl))
|
||||
];
|
||||
else
|
||||
msgBody = [page];
|
||||
var msgBuilder = new DiscordMessageBuilder()
|
||||
.EnableV2Components()
|
||||
.AddContainerComponent(
|
||||
new(
|
||||
msgBody,
|
||||
color: Config.Colors.DownloadLinks
|
||||
)
|
||||
);
|
||||
result.Add(msgBuilder);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<DiscordTextDisplayComponent> Split(StringBuilder content)
|
||||
{
|
||||
var lines = content.ToString().TrimEnd().Split(Environment.NewLine);
|
||||
var isMultiPage = content.Length > 4001;
|
||||
var title = lines[0];
|
||||
var result = new List<DiscordTextDisplayComponent>();
|
||||
content.Clear();
|
||||
content.Append(title);
|
||||
if (isMultiPage)
|
||||
content.Append(" [Page 1 of 2]");
|
||||
foreach (var l in lines.Skip(1))
|
||||
{
|
||||
check:
|
||||
if (content.Length + l.Length + 1 <= 4000)
|
||||
content.Append('\n').Append(l);
|
||||
else if (content.Length is 0)
|
||||
content.Append(l.Trim(4000));
|
||||
else
|
||||
{
|
||||
result.Add(new(content.ToString()));
|
||||
content.Clear();
|
||||
content.Append(title).Append(" [Page 2 of 2]");
|
||||
goto check;
|
||||
}
|
||||
}
|
||||
result.Add(new(content.ToString()));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetLinkName(string link)
|
||||
{
|
||||
var fname = Path.GetFileName(link);
|
||||
if (fname.EndsWith("-PE.pkg"))
|
||||
fname = fname[..^7] + fname[^4..];
|
||||
try
|
||||
{
|
||||
var match = PsnScraper.ContentIdMatcher().Match(fname);
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
<PackageReference Include="DuoVia.FuzzyStrings" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NUnit" Version="4.3.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0">
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.9.2">
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.10.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
Reference in New Issue
Block a user