!download command to search for games

This commit is contained in:
13xforever
2019-03-05 21:04:19 +05:00
parent 4747081433
commit dca59dbaa9
9 changed files with 176 additions and 8 deletions

View File

@@ -5,19 +5,19 @@
// api endpoints, oauth, oauth authorize, telemetry, localization options, billing template, locales, country names, topup settings, paypal sandbox settings, gct, apm, sofort, ...
// this is item #6 in App array
public class AppLocales
public sealed class AppLocales
{
public string[] EnabledLocales; // "ar-AE",...
public AppLocaleOverride[] Overrides;
}
public class AppLocaleOverride
public sealed class AppLocaleOverride
{
public AppLocaleOverrideCriteria Criteria;
public string GensenLocale; // "ar-AE"
}
public class AppLocaleOverrideCriteria
public sealed class AppLocaleOverrideCriteria
{
public string Language; // "ar"
public string Country; // "AE|BH|KW|LB|OM|QA|SA"

View File

@@ -22,7 +22,10 @@ namespace PsnClient.POCOs
public bool? NsxPsPlusUpsell;
public int? TemplateId;
public string ThumbnailUrlBase;
public int? Start;
public int? Size;
public int TotalResults;
public string Query;
public ContainerBanner[] Banners;
public ContainerFacet[] Facets;
public ContainerPromoBackground[] PromoBackgrounds;

View File

@@ -302,6 +302,37 @@ namespace PsnClient
}
}
public async Task<Container> SearchAsync(string locale, string search, CancellationToken cancellationToken)
{
try
{
var loc = locale.AsLocaleData();
var searchId = Uri.EscapeUriString(search);
var queryId = Uri.EscapeDataString(searchId);
var uri = new Uri($"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/faceted-search/{searchId}?query={queryId}&game_content_type=games&size=30&bucket=games&platform=ps3&start=0");
using (var message = new HttpRequestMessage(HttpMethod.Get, uri))
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
try
{
if (response.StatusCode == HttpStatusCode.NotFound)
return null;
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
return await response.Content.ReadAsAsync<Container>(dashedFormatters, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
ConsoleLogger.PrintError(e, response);
return null;
}
}
catch (Exception e)
{
ApiConfig.Log.Error(e);
return null;
}
}
private async Task<string> GetSessionCookies(string locale, CancellationToken cancellationToken)
{
var loc = locale.AsLocaleData();

View File

@@ -138,7 +138,7 @@ Example usage:
await ch.SendMessageAsync(embed: embed).ConfigureAwait(false);
}
[Group("latest"), Aliases("download"), TriggersTyping]
[Group("latest"), TriggersTyping]
[Description("Provides links to the latest RPCS3 build")]
[Cooldown(1, 30, CooldownBucketType.Channel)]
public sealed class UpdatesCheck: BaseCommandModuleCustom

View File

@@ -338,5 +338,10 @@ namespace CompatBot.Commands
var ch = await ctx.GetChannelForSpamAsync().ConfigureAwait(false);
await ch.SendMessageAsync($"{ctx.User.Mention} congratulations, you're the meme").ConfigureAwait(false);
}
}
[Command("download"), Cooldown(1, 20, CooldownBucketType.Channel)]
[Description("Find games to download")]
public Task Download(CommandContext ctx, [RemainingText] string game)
=> Psn.SearchForGame(ctx, game);
}
}

View File

@@ -11,14 +11,11 @@ using DSharpPlus.CommandsNext;
using DSharpPlus.CommandsNext.Attributes;
using DSharpPlus.Entities;
using DSharpPlus.Interactivity;
using PsnClient;
namespace CompatBot.Commands
{
internal sealed partial class Psn
{
private static readonly Client Client = new Client();
[Group("check")]
[Description("Commands to check for various stuff on PSN")]
public sealed class Check: BaseCommandModuleCustom

View File

@@ -4,9 +4,16 @@ using System.Linq;
using System.Threading.Tasks;
using CompatBot.Commands.Attributes;
using CompatBot.Database;
using CompatBot.Database.Providers;
using CompatBot.Utils;
using DSharpPlus.CommandsNext;
using DSharpPlus.CommandsNext.Attributes;
using DSharpPlus.Entities;
using DSharpPlus.Interactivity;
using Google.Apis.Http;
using Microsoft.EntityFrameworkCore.Migrations;
using PsnClient;
using PsnClient.POCOs;
namespace CompatBot.Commands
{
@@ -14,6 +21,8 @@ namespace CompatBot.Commands
[Description("Commands related to PSN metadata")]
internal sealed partial class Psn: BaseCommandModuleCustom
{
private static readonly Client Client = new Client();
[Command("fix"), RequiresBotModRole]
[Description("Reset thumbnail cache for specified product")]
public async Task Fix(CommandContext ctx, [Description("Product ID to reset")] string productId)
@@ -47,6 +56,74 @@ namespace CompatBot.Commands
await ctx.ReactWithAsync(Config.Reactions.Success, "Reset state timestamps").ConfigureAwait(false);
}
[Command("search")]
[Description("Provides game information from PSN")]
public Task Search(CommandContext ctx, [RemainingText] string search)
=> SearchForGame(ctx, search);
public static async Task SearchForGame(CommandContext ctx, [RemainingText] string search)
{
DiscordMessage msg = null;
if (string.IsNullOrEmpty(search))
{
var interact = ctx.Client.GetInteractivity();
msg = await msg.UpdateOrCreateMessageAsync(ctx.Channel, "What game are you looking for?").ConfigureAwait(false);
var response = await interact.WaitForMessageAsync(m => m.Author == ctx.User).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
msg = null;
if (string.IsNullOrEmpty(response?.Message?.Content))
{
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);
return;
}
search = response.Message.Content;
}
var msgTask = msg.UpdateOrCreateMessageAsync(ctx.Channel, "Searching...");
var psnResponseUSTask = Client.SearchAsync("en-US", search, Config.Cts.Token);
var psnResponseEUTask = Client.SearchAsync("en-GB", search, Config.Cts.Token);
var psnResponseJPTask = Client.SearchAsync("ja-JP", search, Config.Cts.Token);
await Task.WhenAll(msgTask, psnResponseUSTask, psnResponseEUTask, psnResponseJPTask).ConfigureAwait(false);
var responseUS = await psnResponseUSTask.ConfigureAwait(false);
var responseEU = await psnResponseEUTask.ConfigureAwait(false);
var responseJP = await psnResponseJPTask.ConfigureAwait(false);
msg = await msgTask.ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
msg = null;
var usGame = GetBestMatch(responseUS.Included, search);
var euGame = GetBestMatch(responseEU.Included, search);
var jpGame = GetBestMatch(responseJP.Included, search);
var hasResults = false;
foreach (var (g, region, locale) in new[]{(usGame, "US", "en-US"), (euGame, "EU", "en-GB"), (jpGame, "JP", "ja-JP")}.Where(i => i.Item1 != null))
{
var thumb = await ThumbnailProvider.GetEmbeddableUrlAsync(ctx.Client, g.Id, g.Attributes.ThumbnailUrlBase).ConfigureAwait(false);
var score = g.Attributes.StarRating?.Score == null ? "N/A" : $"{StringUtils.GetStars(g.Attributes.StarRating?.Score)} ({g.Attributes.StarRating?.Score})";
var result = new DiscordEmbedBuilder
{
Color = new DiscordColor(0x0071cd),
Title = $"⏬ {g.Attributes.Name} [{region}] ({g.Attributes.FileSize?.Value} {g.Attributes.FileSize?.Unit})",
Url = $"https://store.playstation.com/{locale}/product/{g.Id}",
Description = $"Rating: {score}",
ThumbnailUrl = thumb.url,
};
hasResults = true;
await ctx.RespondAsync(embed: result).ConfigureAwait(false);
}
if (!hasResults)
await msg.UpdateOrCreateMessageAsync(ctx.Channel, "No results").ConfigureAwait(false);
}
private static ContainerIncluded GetBestMatch(ContainerIncluded[] included, string search)
{
return (
from i in included
where (i.Type == "game" || i.Type == "legacy-sku") && (i.Attributes.TopCategory != "demo" && i.Attributes.GameContentType != "Demo")
let m = new {score = search.GetFuzzyCoefficientCached(i.Attributes.Name), item = i}
where m.score > 0.3 || (i.Attributes.Name?.StartsWith(search, StringComparison.InvariantCultureIgnoreCase) ?? false)
orderby m.score descending
select m.item
).FirstOrDefault();
}
private static async Task TryDeleteThumbnailCache(CommandContext ctx, List<(string contentId, string link)> linksToRemove)
{
var contentIds = linksToRemove.ToDictionary(l => l.contentId, l => l.link);

View File

@@ -125,5 +125,34 @@ namespace CompatBot.Database.Providers
return title;
}
}
public static async Task<(string url, byte[] image)> GetEmbeddableUrlAsync(DiscordClient client, string contentId, string url)
{
try
{
using (var imgStream = await HttpClient.GetStreamAsync(url).ConfigureAwait(false))
using (var memStream = new MemoryStream())
{
await imgStream.CopyToAsync(memStream).ConfigureAwait(false);
// minimum jpg size is 119 bytes, png is 67 bytes
if (memStream.Length < 64)
return (null, null);
memStream.Seek(0, SeekOrigin.Begin);
var spam = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
if (string.IsNullOrEmpty(Path.GetExtension(url)))
{
var message = await spam.SendFileAsync(contentId + ".jpg", memStream, contentId).ConfigureAwait(false);
url = message.Attachments.First().Url;
}
return (url, memStream.ToArray());
}
}
catch (Exception e)
{
Config.Log.Warn(e);
}
return (null, null);
}
}
}

View File

@@ -170,6 +170,32 @@ namespace CompatBot.Utils
return s.PadRight(totalWidth, padding);
}
public static string GetStars(decimal? stars)
{
if (!stars.HasValue)
return null;
var fullStars = (int)stars;
var halfStar = Math.Round((stars.Value - fullStars)*4, MidpointRounding.ToEven);
var noStars = 5 - (halfStar > 0 && halfStar < 4 ? 1 : 0) - fullStars;
var result = "";
for (var i = 0; i < fullStars; i++)
result += "🌕";
if (halfStar > 3)
;
else if (halfStar > 2)
result += "🌖";
else if (halfStar > 1)
result += "🌗";
else if (halfStar > 0)
result += "🌘";
for (var i = 0; i < noStars; i++)
result += "🌑";
return result;
}
private static bool IsFormat(char c) => SpaceCharacters.Contains(c);
private static string CreateTrimmedString(string str, int start, int end)