Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 110 additions & 3 deletions hasheous-lib/Classes/Config.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Data;
using Newtonsoft.Json;
using IGDB.Models;
using hasheous_server.Classes.Metadata;
using StackExchange.Redis;
using IGDB.Models;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Newtonsoft.Json;
using StackExchange.Redis;

namespace Classes
{
Expand Down Expand Up @@ -135,6 +135,17 @@ public static ConfigFile.GiantBomb GiantBomb
}
}

/// <summary>
/// Gets the ScreenScraper configuration settings.
/// </summary>
public static ConfigFile.ScreenScraper ScreenScraperConfiguration
{
get
{
return _config.ScreenScraperConfiguration;
}
}

/// <summary>
/// Gets the social authentication configuration settings.
/// </summary>
Expand Down Expand Up @@ -567,6 +578,11 @@ public class ConfigFile
/// </summary>
public GiantBomb GiantBombConfiguration = new GiantBomb();

/// <summary>
/// Gets or sets the ScreenScraper configuration settings.
/// </summary>
public ScreenScraper ScreenScraperConfiguration = new ScreenScraper();

/// <summary>
/// Gets or sets the social authentication configuration settings.
/// </summary>
Expand Down Expand Up @@ -923,6 +939,14 @@ public string LibraryMetadataDirectory_WHDLoad
}
}

public string LibraryMetadataDirectory_Screenscraper
{
get
{
return Path.Combine(LibraryMetadataDirectory, "Screenscraper");
}
}

/// <summary>
/// Gets the directory path for MAME Redump metadata within the library metadata directory.
/// </summary>
Expand Down Expand Up @@ -1290,6 +1314,89 @@ private static string _DefaultAPIKey
public string BaseURL = "https://www.giantbomb.com/";
}

/// <summary>
/// Represents the ScreenScraper configuration settings.
/// </summary>
public class ScreenScraper
{
private static string _DefaultClientId
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("screenscraperclientid")))
{
return Environment.GetEnvironmentVariable("screenscraperclientid");
}
else
{
return "";
}
}
}

private static string _DefaultSecret
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("screenscraperclientsecret")))
{
return Environment.GetEnvironmentVariable("screenscraperclientsecret");
}
else
{
return "";
}
}
}

private static string _DefaultDevClientId
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("screenscraperdevclientid")))
{
return Environment.GetEnvironmentVariable("screenscraperdevclientid");
}
else
{
return "";
}
}
}

private static string _DefaultDevSecret
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("screenscraperdevclientsecret")))
{
return Environment.GetEnvironmentVariable("screenscraperdevclientsecret");
}
else
{
return "";
}
}
}

/// <summary>
/// Gets or sets the ScreenScraper client ID used for authenticating API requests.
/// </summary>
public string ClientId = _DefaultClientId;
/// <summary>
/// Gets or sets the ScreenScraper client secret used for authenticating API requests.
/// </summary>
public string Secret = _DefaultSecret;
/// <summary>
/// Gets or sets the ScreenScraper developer client ID used for authenticating API requests in development environments.
/// </summary>
public string DevClientId = _DefaultDevClientId;
/// <summary>
/// Gets or sets the ScreenScraper developer client secret used for authenticating API requests in development environments.
/// </summary>
public string DevSecret = _DefaultDevSecret;
}

/// <summary>
/// Represents the social authentication configuration settings, including Google and Microsoft OAuth credentials.
/// </summary>
Expand Down
114 changes: 102 additions & 12 deletions hasheous-lib/Classes/DataObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ public async Task<AttributeItem> GetRoms(List<Dictionary<string, object>> GameSi
return attribute;
}

public async Task<Models.DataObjectItem> NewDataObject(DataObjectType objectType, Models.DataObjectItemModel model, ApplicationUser? user = null)
public async Task<Models.DataObjectItem> NewDataObject(DataObjectType objectType, Models.DataObjectItemModel model, ApplicationUser? user = null, bool allowSearch = true)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO DataObject (`Name`, `ObjectType`, `CreatedDate`, `UpdatedDate`) VALUES (@name, @objecttype, @createddate, @updateddate); SELECT LAST_INSERT_ID();";
Expand Down Expand Up @@ -1184,7 +1184,10 @@ public async Task<AttributeItem> GetRoms(List<Dictionary<string, object>> GameSi
}
}

await DataObjectMetadataSearch(objectType, (long)(ulong)data.Rows[0][0]);
if (allowSearch)
{
await DataObjectMetadataSearch(objectType, (long)(ulong)data.Rows[0][0]);
}
break;

case DataObjectType.App:
Expand Down Expand Up @@ -1894,13 +1897,25 @@ public async Task DataObjectMetadataSearch(DataObjectType objectType, long? id,
// get all metadata sources
private static MetadataSources[] allMetadataSources = (MetadataSources[])Enum.GetValues(typeof(MetadataSources));

// Tokens commonly found in titles/platform names that should not be interpreted as Roman numerals.
private static readonly List<string> RomanConversionAcronymExclusions = new List<string>
{
"CD",
"DVD",
"PS",
"PSP",
"VR",
"3DO"
};

private async Task _DataObjectMetadataSearch(DataObjectType objectType, long? id, bool ForceSearch)
{
HashSet<MetadataSources> ProcessSources = [
MetadataSources.IGDB,
MetadataSources.TheGamesDb,
MetadataSources.RetroAchievements,
MetadataSources.GiantBomb
MetadataSources.GiantBomb,
MetadataSources.ScreenScraper
];

// set now time
Expand Down Expand Up @@ -2138,6 +2153,13 @@ private async Task _DataObjectMetadataSearch_Apply(DataObjectItem item, string l
continue;
}

// check if the provider is enabled
if (!metadataHandler.Enabled)
{
Logging.Log(Logging.LogType.Warning, "Metadata Match", $"Metadata provider for source {metadataSource} is not enabled. Skipping.");
continue;
}

// get the metadataitem from the dataobject - if not present, create a new one
// default to new
DataObjectItem.MetadataItem metadata = new DataObjectItem.MetadataItem(objectType)
Expand Down Expand Up @@ -2195,6 +2217,12 @@ private async Task _DataObjectMetadataSearch_Apply(DataObjectItem item, string l

Logging.Log(Logging.LogType.Information, "Metadata Match", $"{processedObjectCount} / {objectTotalCount} - {item.ObjectType} {item.Name} {metadata.MatchMethod} to {metadata.Source} metadata: {metadata.Id}");
}
catch (MetadataLib.MetadataRateLimitException ex)
{
Logging.Log(Logging.LogType.Warning, "Metadata Match", $"{processedObjectCount} / {objectTotalCount} - Rate limit reached, retry after {ex.RetryAfter}", ex);
metadata.NextSearch = ex.RetryAfter;
metadataUpdates.Add(metadata);
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Metadata Match", $"{processedObjectCount} / {objectTotalCount} - Error processing metadata search", ex);
Expand Down Expand Up @@ -2328,6 +2356,7 @@ public static List<string> GetSearchCandidates(string GameName)
}

List<string> searchCandidates = new List<string>();
List<string> lowPriorityCandidates = new List<string>();

string NormalizeWhitespace(string value)
{
Expand All @@ -2348,6 +2377,15 @@ void AddCandidate(string value)
}
}

void AddLowPriorityCandidate(string value)
{
string normalized = NormalizeWhitespace(value);
if (!string.IsNullOrWhiteSpace(normalized))
{
lowPriorityCandidates.Add(normalized);
}
}

string baseName = NormalizeWhitespace(GameName);
AddCandidate(baseName);

Expand Down Expand Up @@ -2379,15 +2417,46 @@ void AddCandidate(string value)

void AddDelimiterVariants(string value)
{
if (value.Contains(" - ", StringComparison.Ordinal))
// normalize and expand delimiter variations so comparisons can match API results
// regardless of whether separators are spaces, hyphens, or colons.
string hyphenTight = Regex.Replace(value, @"\s*-\s*", "-");
string hyphenSpaced = Regex.Replace(value, @"\s*-\s*", " - ");
string hyphenToSpace = Regex.Replace(value, @"\s*-\s*", " ");
string hyphenToColon = Regex.Replace(value, @"\s*-\s*", ": ");

AddCandidate(hyphenTight);
AddCandidate(hyphenSpaced);
AddCandidate(hyphenToSpace);
AddCandidate(hyphenToColon);

// if a value has spaces but no hyphens, generate a hyphenated variant.
if (!value.Contains("-", StringComparison.Ordinal) && value.Contains(" ", StringComparison.Ordinal))
{
AddCandidate(value.Replace(" - ", ": "));
AddCandidate(value.Replace(" - ", " "));
AddCandidate(Regex.Replace(value, @"\s+", "-"));
}

if (value.Contains(": ", StringComparison.Ordinal))
if (value.Contains(":", StringComparison.Ordinal))
{
AddCandidate(value.Replace(": ", " "));
AddCandidate(Regex.Replace(value, @"\s*:\s*", " "));
AddCandidate(Regex.Replace(value, @"\s*:\s*", " - "));
}

if (value.Contains("/", StringComparison.Ordinal))
{
// Slash variants are useful but noisy; keep these as lower-priority candidates.
AddLowPriorityCandidate(Regex.Replace(value, @"\s*/\s*", "/"));
AddLowPriorityCandidate(Regex.Replace(value, @"\s*/\s*", " "));
AddLowPriorityCandidate(Regex.Replace(value, @"\s*/\s*", " - "));

string[] slashParts = Regex.Split(value, @"\s*/\s*")
.Where(part => !string.IsNullOrWhiteSpace(part))
.ToArray();

if (slashParts.Length == 2)
{
AddLowPriorityCandidate(slashParts[0]);
AddLowPriorityCandidate(slashParts[1]);
}
}
}

Expand Down Expand Up @@ -2448,7 +2517,20 @@ void AddArticleVariants(string value)
{
string romanConverted = Regex.Replace(candidate, @"\b[IVXLCDM]+\b", match =>
{
return Common.RomanNumerals.RomanToInt(match.Value).ToString();
string token = match.Value;

if (RomanConversionAcronymExclusions.Contains(token, StringComparer.OrdinalIgnoreCase))
{
return token;
}

int parsed = Common.RomanNumerals.RomanToInt(token);
if (parsed >= 1 && parsed <= 30)
{
return parsed.ToString();
}

return token;
}, RegexOptions.IgnoreCase);

if (!string.Equals(romanConverted, candidate, StringComparison.Ordinal))
Expand All @@ -2462,7 +2544,7 @@ void AddArticleVariants(string value)
{
string numberToRoman = Regex.Replace(candidate, @"\b(\d+)\b", match =>
{
if (int.TryParse(match.Groups[1].Value, out int num) && num >= 1 && num <= 3999)
if (int.TryParse(match.Groups[1].Value, out int num) && num >= 1 && num <= 30)
{
return Common.RomanNumerals.IntToRoman(num);
}
Expand All @@ -2481,7 +2563,7 @@ void AddArticleVariants(string value)
// Convert numbers to words
string numberToWords = Regex.Replace(candidate, @"\b(\d+)\b", match =>
{
if (int.TryParse(match.Groups[1].Value, out int num) && num >= 0 && num <= 999999999)
if (int.TryParse(match.Groups[1].Value, out int num) && num >= 1 && num <= 30)
{
return Common.Numbers.NumberToWords(num);
}
Expand All @@ -2497,7 +2579,12 @@ void AddArticleVariants(string value)
string wordsToNumber = Regex.Replace(candidate, @"\b(?:Zero|One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Eleven|Twelve|Thirteen|Fourteen|Fifteen|Sixteen|Seventeen|Eighteen|Nineteen|Twenty|Thirty|Forty|Fifty|Sixty|Seventy|Eighty|Ninety|Hundred|Thousand|Million|Billion)(?:\s+(?:Zero|One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Eleven|Twelve|Thirteen|Fourteen|Fifteen|Sixteen|Seventeen|Eighteen|Nineteen|Twenty|Thirty|Forty|Fifty|Sixty|Seventy|Eighty|Ninety|Hundred|Thousand|Million|Billion))*\b", match =>
{
var result = Common.Numbers.WordsToNumbers(match.Value);
return result.HasValue ? result.Value.ToString() : match.Value;
if (result.HasValue && result.Value >= 1 && result.Value <= 30)
{
return result.Value.ToString();
}

return match.Value;
}, RegexOptions.IgnoreCase);

if (!string.Equals(wordsToNumber, candidate, StringComparison.Ordinal))
Expand All @@ -2506,6 +2593,9 @@ void AddArticleVariants(string value)
}
}

// keep lower-confidence slash-derived candidates at the end to emulate weighting.
searchCandidates.AddRange(lowPriorityCandidates);

// remove duplicates while preserving order
List<string> distinctCandidates = new List<string>();
HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Expand Down
3 changes: 3 additions & 0 deletions hasheous-lib/Classes/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,9 @@ public void BuildTableFromType(string databaseName, string prefix, Type type, st
case "IdentitiesOrValues`1":
columnType = "LONGTEXT";
break;
default:
columnType = "LONGTEXT";
break;
}
}

Expand Down
Loading
Loading