Skip to content

Commit

Permalink
Merge pull request #5 from minhnhattonthat/v0.3.1
Browse files Browse the repository at this point in the history
V0.3.1
  • Loading branch information
minhnhattonthat authored Feb 17, 2024
2 parents 68edf5f + 4577db4 commit 12a1217
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 101 deletions.
7 changes: 1 addition & 6 deletions Client/AsiaNintendoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,7 @@ public override List<NintendoGame> SearchGames(string normalizedSearchName)
logger.Error(e, "Error performing search");
}

var words = new Regex(@"(?!\\.)\W").Split(normalizedSearchName);
string regex = string.Join("|", words);
return results
.Where(game => Regex.IsMatch(game.Name.ToLower(), regex))
.OrderByDescending(game => Regex.Matches(game.Name.ToLower(), regex).Count)
.ToList();
return results.OrderByRelevance(normalizedSearchName);
}

public override NintendoGame GetGameDetails(NintendoGame game)
Expand Down
45 changes: 36 additions & 9 deletions Client/UKNintendoClient.cs → Client/EuropeNintendoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,42 @@

namespace NintendoMetadata.Client
{
public class UKNintendoClient : NintendoClient
public class EuropeNintendoClient : NintendoClient
{

protected override string BaseUrl => "http://search.nintendo-europe.com/en/select";

public UKNintendoClient(MetadataRequestOptions options, NintendoMetadataSettings settings) : base(options, settings)
public EuropeNintendoClient(MetadataRequestOptions options, NintendoMetadataSettings settings) : base(options, settings)
{
}

public override List<NintendoGame> SearchGames(string normalizedSearchName)
{
List<NintendoGame> results = new List<NintendoGame>();
var platform = options.GetPlatform();
string playableOnTxt;
switch(platform)
{
case NintendoPlatform.Nintendo3DS:
playableOnTxt = "CTR";
break;
case NintendoPlatform.NintendoWii:
playableOnTxt = "RVL";
break;
case NintendoPlatform.NintendoWiiU:
playableOnTxt = "WUP";
break;
case NintendoPlatform.NintendoSwitch:
default:
playableOnTxt = "HAC";
break;
}

var request = new RestRequest("/");
var parameters = new
{
q = normalizedSearchName,
fq = "type:GAME AND system_type:nintendoswitch*",
fq = $@"type:GAME AND playable_on_txt:""{playableOnTxt}""",
sort = "score desc, date_from desc",
start = 0,
rows = 24,
Expand All @@ -52,7 +70,7 @@ public override List<NintendoGame> SearchGames(string normalizedSearchName)

foreach (dynamic game in docs)
{
NintendoGame g = NintendoGame.ParseUkGame(game);
NintendoGame g = NintendoGame.ParseEuropeGame(game);
results.Add(g);
}
}
Expand All @@ -61,7 +79,7 @@ public override List<NintendoGame> SearchGames(string normalizedSearchName)
logger.Error(e, "Error performing search");
}

return results.OrderBy(game => NameStringCompare(normalizedSearchName, game.Name)).ToList();
return results.OrderByRelevance(normalizedSearchName);
}

public override NintendoGame GetGameDetails(NintendoGame game)
Expand All @@ -78,12 +96,21 @@ public override NintendoGame GetGameDetails(NintendoGame game)
OverrideEncoding = Encoding.UTF8
};
var doc = web.Load(link.Url);
var descriptionNode = doc.DocumentNode.SelectSingleNode(@"//section[@id='Overview']");
if (descriptionNode != null)
var fullDescription = "";
var descriptionNodes = doc.DocumentNode.SelectNodes(@"//section[@id='Overview']//div[contains(@class, 'row-content')]/div");
foreach (var descriptionNode in descriptionNodes)
{
var text = descriptionNode.InnerHtml.Trim();
text = Regex.Replace(text, @"\s{2,}", "");
text = Regex.Replace(text, @" class=""(.*?)""", "");
fullDescription += text;
}
if (fullDescription.StartsWith(@"<p>"))
{
game.FullDescription = Regex.Replace(descriptionNode.InnerHtml, @"\s{2,}", "");
logger.Info(game.FullDescription);
fullDescription = fullDescription.Substring(3, fullDescription.Length - 7);
}
game.FullDescription = fullDescription;
logger.Info(game.FullDescription);

return game;
}
Expand Down
2 changes: 1 addition & 1 deletion Client/JapanNintendoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public override List<NintendoGame> SearchGames(string normalizedSearchName)
logger.Error(e, "Error performing search");
}

return results.OrderBy(game => NameStringCompare(normalizedSearchName, game.Name)).ToList();
return results;
}

public override NintendoGame GetGameDetails(NintendoGame game)
Expand Down
44 changes: 15 additions & 29 deletions Client/USANintentdoClient.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
using Playnite.SDK.Plugins;
using Playnite.SDK;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RestSharp;
using Newtonsoft.Json.Linq;
using HtmlAgilityPack;
using Playnite.SDK.Models;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls;
using Newtonsoft.Json;

namespace NintendoMetadata.Client
{
Expand Down Expand Up @@ -40,7 +35,14 @@ public override List<NintendoGame> SearchGames(string normalizedSearchName)
List<NintendoGame> results = new List<NintendoGame>();

var request = new RestRequest("/", Method.Post);
var body = $@"{{""requests"": [{{""indexName"": ""store_game_en_us"",""query"": ""{normalizedSearchName}"",""params"": ""hitsPerPage=10""}}]}}";
var requestItem = new
{
indexName = "store_game_en_us",
query = normalizedSearchName,
facetFilters = new[] { "corePlatforms:Nintendo Switch", "hasDlc:false" },
hitsPerPage = 10,
};
var body = $@"{{""requests"": [{JsonConvert.SerializeObject(requestItem)}]}}";
request.RequestFormat = DataFormat.Json;
request.AddBody(body);

Expand Down Expand Up @@ -68,7 +70,7 @@ public override List<NintendoGame> SearchGames(string normalizedSearchName)
logger.Error(e, "Error performing search");
}

return results.OrderBy(game => NameStringCompare(normalizedSearchName, game.Name)).ToList();
return results.OrderByRelevance(normalizedSearchName);
}

public override NintendoGame GetGameDetails(NintendoGame game)
Expand All @@ -82,27 +84,11 @@ public override NintendoGame GetGameDetails(NintendoGame game)

var web = new HtmlWeb();
var doc = web.Load(link.Url);
var descriptionNode = doc.DocumentNode.SelectSingleNode(@"//div[@class='ProductDetailstyles__Grid-sc-4l5ex7-4 hKLOzA']//p");
if (descriptionNode != null)
{
game.FullDescription = descriptionNode.InnerHtml;
logger.Info(game.FullDescription);
}
else
{
var nextData = doc.DocumentNode.SelectSingleNode(@"//script[@id='__NEXT_DATA__']");
if (nextData != null)
{
var matches = Regex.Matches(nextData.InnerText, @"""description"":""(.*?)""");
if (matches.Count > 1 && matches[matches.Count - 1].Success)
{
string fullDescription = Regex.Unescape(matches[matches.Count - 1].Groups[1].Value);
fullDescription = fullDescription.Substring(3, fullDescription.Length - 7);
game.FullDescription = fullDescription;
}
//var json = JObject.Parse(nextData.InnerText);
}
}
var dataNode = doc.DocumentNode.SelectSingleNode(@"//script[@id='__NEXT_DATA__']");
var dataJson = JObject.Parse(dataNode.InnerText);
var sku = (string)dataJson.SelectToken($@"props.pageProps.analytics.product.sku");
var fullDescription = (string)dataJson.SelectToken($@"props.pageProps.initialApolloState.StoreProduct:{{""sku"":""{sku}"",""locale"":""en_US""}}.description");
game.FullDescription = fullDescription.Substring(3, fullDescription.Length - 7);

return game;
}
Expand Down
66 changes: 65 additions & 1 deletion NintendoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace NintendoMetadata
{
Expand Down Expand Up @@ -67,9 +70,70 @@ protected JObject ExecuteRequest(RestRequest request)

return JObject.Parse(content);
}
}

public static class NintendoClientExtensions
{
public static string[] SplitToWords(this string input)
{
return new Regex(@"(?!\\.)\W").Split(input);
}

public static string NormalizeGameName(this string input)
{
var japaneseRegex = new Regex(@"[\u3000-\u303F]|[\u3040-\u309F]|[\u30A0-\u30FF]|[\uFF00-\uFFEF]|[\u4E00-\u9FAF]|[\u2605-\u2606]|[\u2190-\u2195]|\u203B/g");
if (japaneseRegex.IsMatch(input))
{
return input;
}

// to lower case
string output = input.ToLower();

// remove additional name elements
output = new Regex(@"\[.*?\]").Replace(output, "");
output = new Regex(@"\(.*?\)").Replace(output, "");

// remove all accents
var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(output);
output = Encoding.ASCII.GetString(bytes);

// remove invalid chars
output = Regex.Replace(output, @"[^a-z0-9\s-]", "");

// convert multiple spaces into one space
output = Regex.Replace(output, @"\s+", " ").Trim();

return output;
}

public static List<NintendoGame> OrderByRelevance(this List<NintendoGame> list, string normalizedSearchName)
{
var words = new Regex(@"(?!\\.)\W").Split(normalizedSearchName);
string regex = string.Concat(words.Select(w => $@"(?=.*\b{w}\b)"));
return list
.Where(game => Regex.IsMatch(game.Name.NormalizeGameName(), regex))
.OrderBy(game => NameStringCompare(normalizedSearchName, game.Name.NormalizeGameName()))
.ToList();
}

public static NintendoPlatform GetPlatform(this MetadataRequestOptions options)
{
var nintendoPlatform = NintendoPlatform.NintendoSwitch;
var platformName = options.GameData.Platforms?[0]?.Name;
if (platformName != null)
{
var success = Enum.TryParse<NintendoPlatform>(platformName.Replace(" ", ""), out var parsedPlatform);
if (success)
{
nintendoPlatform = parsedPlatform;
}
}
return nintendoPlatform;
}

// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#C.23
protected int NameStringCompare(string a, string b)
public static int NameStringCompare(string a, string b)
{

if (string.IsNullOrEmpty(a))
Expand Down
28 changes: 16 additions & 12 deletions NintendoGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static NintendoGame ParseUsGame(JObject data)

if (!string.IsNullOrEmpty(developer?.Trim()))
{
if (developer.EndsWith(", LTD."))
if (developer.EndsWith(", LTD.") || developer.EndsWith(", Inc."))
{
result.Developers.Add(new MetadataNameProperty(developer));
}
Expand Down Expand Up @@ -105,14 +105,14 @@ public static NintendoGame ParseUsGame(JObject data)
return result;
}

public static NintendoGame ParseUkGame(JObject data)
public static NintendoGame ParseEuropeGame(JObject data)
{
var result = new NintendoGame
{
Title = (string)data["title"],
FullDescription = (string)data["product_catalog_description_s"],
ReleaseDate = new ReleaseDate((DateTime)data["dates_released_dts"][0]),
NSUID = (string)data["nsuid_txt"][0],
NSUID = (string)data["nsuid_txt"]?[0],
};

var developer = (string)data["softwareDeveloper"];
Expand All @@ -131,11 +131,12 @@ public static NintendoGame ParseUkGame(JObject data)

result.AgeRatings.Add(new MetadataNameProperty((string)data["pretty_agerating_s"]));

result.Links.Add(new Link("My Nintendo Store", $"https://www.nintendo.co.uk/{(string)data["url"]}"));

result.Image = new MetadataFile((string)data["image_url_sq_s"]);
result.Links.Add(new Link("My Nintendo Store", $"https://www.nintendo.co.uk{(string)data["url"]}"));

var imageUrl = (string)data["image_url_sq_s"] ?? ((string)data["image_url_tm_s"])?.Replace("300w", "500w") ?? (string)data["image_url"];
result.Image = new MetadataFile(imageUrl);

result.LandscapeImage = new MetadataFile(((string)data["image_url_h2x1_s"]).Replace("500w", "1600w"));
result.LandscapeImage = new MetadataFile(((string)data["image_url_h2x1_s"]).Replace("500w", "1600w") ?? (string)data["image_url"]);

result.Name = result.Title;
result.Description = $"{result.ReleaseDate?.Year}-{result.ReleaseDate?.Month}-{result.ReleaseDate?.Day} | {result.Publishers.First()}";
Expand Down Expand Up @@ -193,7 +194,7 @@ public static NintendoGame ParseAsiaGame(JObject data)
NSUID = (string)data["common"]["nsuid"],
};

string developer = (string)data["common"]["developerName"];
string developer = (string)data.SelectToken("common.developerName");
if (!string.IsNullOrEmpty(developer))
{
result.Developers.Add(new MetadataNameProperty(developer));
Expand All @@ -214,14 +215,17 @@ public static NintendoGame ParseAsiaGame(JObject data)
result.Links.Add(new Link("My Nintendo Store", storeUrl));
}

if (data["common"]["heroImageSquare"].HasValues)
var imageUrl = (string)data.SelectToken("common.heroImageSquare.url");
if (imageUrl != null)
{
var imageUrl = (string)data["common"]["heroImageSquare"]["url"];
result.Image = new MetadataFile(imageUrl);
}

var landscapeImageUrl = (string)data["common"]["heroImage169"]["url"];
result.LandscapeImage = new MetadataFile(landscapeImageUrl);
var landscapeImageUrl = (string)data.SelectToken("common.heroImage169.url");
if (landscapeImageUrl != null)
{
result.LandscapeImage = new MetadataFile(landscapeImageUrl);
}

if (result.Image == null)
{
Expand Down
2 changes: 1 addition & 1 deletion NintendoMetadata.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<ItemGroup>
<Compile Include="Client\AsiaNintendoClient.cs" />
<Compile Include="Client\JapanNintendoClient.cs" />
<Compile Include="Client\UKNintendoClient.cs" />
<Compile Include="Client\EuropeNintendoClient.cs" />
<Compile Include="Client\USANintentdoClient.cs" />
<Compile Include="NintendoMetadata.cs" />
<Compile Include="NintendoMetadataProvider.cs" />
Expand Down
Loading

0 comments on commit 12a1217

Please sign in to comment.