From 4191a42afdfaf2a68b5bdf3cdda06ea014e6b4a9 Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Sat, 1 Nov 2025 13:28:36 -0400 Subject: [PATCH] Moved some classes around to the domain layer. --- .../DLSite/Models/VoiceWorkDetails.cs | 2 +- .../DLSite/Models/VoiceWorkSeries.cs | 10 +-- .../DLSite/Models/VoiceWorkTranslation.cs | 14 ++--- .../Scanning/Contracts/VoiceWorkIngest.cs | 2 +- JSMR.Domain/Enums/TranslationKind.cs | 9 +++ JSMR.Domain/ValueObjects/SupportedLanguage.cs | 11 ++-- JSMR.Domain/ValueObjects/VoiceWorkSeries.cs | 3 + .../ValueObjects/VoiceWorkTranslation.cs | 8 +++ .../Common/Locales/EnglishLocale.cs | 10 +-- JSMR.Infrastructure/Common/Locales/ILocale.cs | 10 +-- .../Common/Locales/JapaneseLocale.cs | 10 +-- .../DLSite/Mapping/DLSiteToDomainMapper.cs | 61 ++++++++----------- .../Scanning/DLSiteSearchFilterBuilder.cs | 16 ++--- .../DLSiteSearchFilterBuilderExtensions.cs | 9 ++- .../Scanning/EnglishVoiceWorksScanner.cs | 6 +- .../Scanning/JapaneseVoiceWorksScanner.cs | 6 +- JSMR.Infrastructure/Scanning/LocaleMap.cs | 16 +++++ .../Scanning/VoiceWorksScanner.cs | 4 +- .../Integrations/DLSite/DLSiteClientTests.cs | 4 +- 19 files changed, 115 insertions(+), 96 deletions(-) create mode 100644 JSMR.Domain/Enums/TranslationKind.cs create mode 100644 JSMR.Domain/ValueObjects/VoiceWorkSeries.cs create mode 100644 JSMR.Domain/ValueObjects/VoiceWorkTranslation.cs create mode 100644 JSMR.Infrastructure/Scanning/LocaleMap.cs diff --git a/JSMR.Application/Integrations/DLSite/Models/VoiceWorkDetails.cs b/JSMR.Application/Integrations/DLSite/Models/VoiceWorkDetails.cs index 4c46ae1..9ab9ad8 100644 --- a/JSMR.Application/Integrations/DLSite/Models/VoiceWorkDetails.cs +++ b/JSMR.Application/Integrations/DLSite/Models/VoiceWorkDetails.cs @@ -11,7 +11,7 @@ public class VoiceWorkDetails public int WishlistCount { get; init; } public int DownloadCount { get; init; } public DateTime? RegistrationDate { get; init; } - public SupportedLanguage[] SupportedLanguages { get; init; } = []; + public IReadOnlyList SupportedLanguages { get; init; } = []; public AIGeneration AI { get; init; } public bool HasTrial { get; init; } public bool HasDLPlay { get; init; } diff --git a/JSMR.Application/Integrations/DLSite/Models/VoiceWorkSeries.cs b/JSMR.Application/Integrations/DLSite/Models/VoiceWorkSeries.cs index 1d1d107..5a0842d 100644 --- a/JSMR.Application/Integrations/DLSite/Models/VoiceWorkSeries.cs +++ b/JSMR.Application/Integrations/DLSite/Models/VoiceWorkSeries.cs @@ -1,7 +1,7 @@ namespace JSMR.Application.Integrations.DLSite.Models; -public class VoiceWorkSeries -{ - public required string Identifier { get; init; } - public required string Name { get; init; } -} \ No newline at end of file +//public class VoiceWorkSeries +//{ +// public required string Identifier { get; init; } +// public required string Name { get; init; } +//} \ No newline at end of file diff --git a/JSMR.Application/Integrations/DLSite/Models/VoiceWorkTranslation.cs b/JSMR.Application/Integrations/DLSite/Models/VoiceWorkTranslation.cs index f94d7fb..b935085 100644 --- a/JSMR.Application/Integrations/DLSite/Models/VoiceWorkTranslation.cs +++ b/JSMR.Application/Integrations/DLSite/Models/VoiceWorkTranslation.cs @@ -2,10 +2,10 @@ namespace JSMR.Application.Integrations.DLSite.Models; -public class VoiceWorkTranslation -{ - public required string OriginalProductId { get; init; } - public bool IsOfficialTranslation { get; init; } - public bool IsRecommendedTranslation { get; init; } - public required Language Language { get; init; } -} \ No newline at end of file +//public class VoiceWorkTranslation +//{ +// public required string OriginalProductId { get; init; } +// public bool IsOfficialTranslation { get; init; } +// public bool IsRecommendedTranslation { get; init; } +// public required Language Language { get; init; } +//} \ No newline at end of file diff --git a/JSMR.Application/Scanning/Contracts/VoiceWorkIngest.cs b/JSMR.Application/Scanning/Contracts/VoiceWorkIngest.cs index 5192371..401c954 100644 --- a/JSMR.Application/Scanning/Contracts/VoiceWorkIngest.cs +++ b/JSMR.Application/Scanning/Contracts/VoiceWorkIngest.cs @@ -21,7 +21,7 @@ public sealed record VoiceWorkIngest public int? Votes { get; init; } public AgeRating AgeRating { get; init; } public bool HasImage { get; init; } - public ICollection SupportedLanguages { get; init; } = []; + public IReadOnlyList SupportedLanguages { get; init; } = []; public DateOnly? ExpectedDate { get; init; } public DateOnly? SalesDate { get; init; } public DateTime? RegistrationDate { get; init; } diff --git a/JSMR.Domain/Enums/TranslationKind.cs b/JSMR.Domain/Enums/TranslationKind.cs new file mode 100644 index 0000000..93c6fe3 --- /dev/null +++ b/JSMR.Domain/Enums/TranslationKind.cs @@ -0,0 +1,9 @@ +namespace JSMR.Domain.Enums; + +[Flags] +public enum TranslationKind +{ + None = 0, + Official = 1, + Recommended = 2 +} \ No newline at end of file diff --git a/JSMR.Domain/ValueObjects/SupportedLanguage.cs b/JSMR.Domain/ValueObjects/SupportedLanguage.cs index e0c71b9..b1895f4 100644 --- a/JSMR.Domain/ValueObjects/SupportedLanguage.cs +++ b/JSMR.Domain/ValueObjects/SupportedLanguage.cs @@ -1,4 +1,5 @@ using JSMR.Domain.Enums; +using System.Diagnostics.CodeAnalysis; namespace JSMR.Domain.ValueObjects; @@ -26,14 +27,14 @@ public sealed record SupportedLanguage(string Code, Language Language) private static readonly Dictionary _byLang = All.ToDictionary(x => x.Language); - public static bool TryFromCode(string code, out SupportedLanguage? supportedLanguage) => + public static bool TryFromCode(string code, [MaybeNullWhen(false)] out SupportedLanguage supportedLanguage) => _byCode.TryGetValue(code, out supportedLanguage); - public static bool TryFromLanguage(Language lang, out SupportedLanguage? supportedLanguage) => - _byLang.TryGetValue(lang, out supportedLanguage); + public static bool TryFromLanguage(Language language, [MaybeNullWhen(false)] out SupportedLanguage supportedLanguage) => + _byLang.TryGetValue(language, out supportedLanguage); public static SupportedLanguage FromLanguage(Language language) => - _byLang.TryGetValue(language, out var sl) - ? sl + _byLang.TryGetValue(language, out SupportedLanguage? supportedLanguage) + ? supportedLanguage : throw new ArgumentOutOfRangeException(nameof(language), $"Unsupported: {language}"); } \ No newline at end of file diff --git a/JSMR.Domain/ValueObjects/VoiceWorkSeries.cs b/JSMR.Domain/ValueObjects/VoiceWorkSeries.cs new file mode 100644 index 0000000..2a16e36 --- /dev/null +++ b/JSMR.Domain/ValueObjects/VoiceWorkSeries.cs @@ -0,0 +1,3 @@ +namespace JSMR.Domain.ValueObjects; + +public sealed record VoiceWorkSeries(string Identifier, string Name); \ No newline at end of file diff --git a/JSMR.Domain/ValueObjects/VoiceWorkTranslation.cs b/JSMR.Domain/ValueObjects/VoiceWorkTranslation.cs new file mode 100644 index 0000000..a28e547 --- /dev/null +++ b/JSMR.Domain/ValueObjects/VoiceWorkTranslation.cs @@ -0,0 +1,8 @@ +using JSMR.Domain.Enums; + +namespace JSMR.Domain.ValueObjects; + +public sealed record VoiceWorkTranslation( + string OriginalProductId, + Language Language, + TranslationKind Kind = TranslationKind.None); \ No newline at end of file diff --git a/JSMR.Infrastructure/Common/Locales/EnglishLocale.cs b/JSMR.Infrastructure/Common/Locales/EnglishLocale.cs index 6f1805a..4e04f94 100644 --- a/JSMR.Infrastructure/Common/Locales/EnglishLocale.cs +++ b/JSMR.Infrastructure/Common/Locales/EnglishLocale.cs @@ -1,7 +1,7 @@ namespace JSMR.Infrastructure.Common.Locales; -public class EnglishLocale : ILocale -{ - public string Abbreviation => "en"; - public string Code => "en_US"; -} \ No newline at end of file +//public class EnglishLocale : ILocale +//{ +// public string Abbreviation => "en"; +// public string Code => "en_US"; +//} \ No newline at end of file diff --git a/JSMR.Infrastructure/Common/Locales/ILocale.cs b/JSMR.Infrastructure/Common/Locales/ILocale.cs index 64928b7..3f3dce7 100644 --- a/JSMR.Infrastructure/Common/Locales/ILocale.cs +++ b/JSMR.Infrastructure/Common/Locales/ILocale.cs @@ -1,7 +1,7 @@ namespace JSMR.Infrastructure.Common.Locales; -public interface ILocale -{ - string Abbreviation { get; } - string Code { get; } -} \ No newline at end of file +//public interface ILocale +//{ +// string Abbreviation { get; } +// string Code { get; } +//} \ No newline at end of file diff --git a/JSMR.Infrastructure/Common/Locales/JapaneseLocale.cs b/JSMR.Infrastructure/Common/Locales/JapaneseLocale.cs index 0585b40..0415300 100644 --- a/JSMR.Infrastructure/Common/Locales/JapaneseLocale.cs +++ b/JSMR.Infrastructure/Common/Locales/JapaneseLocale.cs @@ -1,7 +1,7 @@ namespace JSMR.Infrastructure.Common.Locales; -public class JapaneseLocale : ILocale -{ - public string Abbreviation => "jp"; - public string Code => "ja_JP"; -} \ No newline at end of file +//public class JapaneseLocale : ILocale +//{ +// public string Abbreviation => "jp"; +// public string Code => "ja_JP"; +//} \ No newline at end of file diff --git a/JSMR.Infrastructure/Integrations/DLSite/Mapping/DLSiteToDomainMapper.cs b/JSMR.Infrastructure/Integrations/DLSite/Mapping/DLSiteToDomainMapper.cs index 78a980b..09679f6 100644 --- a/JSMR.Infrastructure/Integrations/DLSite/Mapping/DLSiteToDomainMapper.cs +++ b/JSMR.Infrastructure/Integrations/DLSite/Mapping/DLSiteToDomainMapper.cs @@ -16,25 +16,6 @@ public static class DLSiteToDomainMapper private const string OptAIFull = "AIG"; private const string OptAIPartial = "AIP"; - private static readonly (string Code, Language Lang)[] SupportedLanguageFlags = - [ - (SupportedLanguage.Japanese.Code, Language.Japanese), - (SupportedLanguage.English.Code, Language.English), - (SupportedLanguage.ChineseTraditional.Code, Language.ChineseTraditional), - (SupportedLanguage.ChineseSimplified.Code, Language.ChineseSimplified) - ]; - - private static readonly (string Code, SupportedLanguage Lang)[] SupportedLanguageFlags2 = - [ - (SupportedLanguage.Japanese.Code, SupportedLanguage.Japanese), - (SupportedLanguage.English.Code, SupportedLanguage.English), - (SupportedLanguage.ChineseTraditional.Code, SupportedLanguage.ChineseTraditional), - (SupportedLanguage.ChineseSimplified.Code, SupportedLanguage.ChineseSimplified) - ]; - - private static readonly Dictionary TranslationLanguageMap = - SupportedLanguageFlags.ToDictionary(x => x.Code, x => x.Lang, StringComparer.OrdinalIgnoreCase); - private static readonly Dictionary AgeRatingMap = new() { { 1, AgeRating.AllAges }, @@ -89,11 +70,10 @@ public static class DLSiteToDomainMapper if (string.IsNullOrWhiteSpace(productInfo.TitleId) || string.IsNullOrWhiteSpace(productInfo.TitleName)) return null; - return new VoiceWorkSeries - { - Identifier = productInfo.TitleId, - Name = productInfo.TitleName - }; + return new VoiceWorkSeries( + Identifier: productInfo.TitleId, + Name: productInfo.TitleName + ); } private static VoiceWorkTranslation? MapTranslation(ProductInfo info, HashSet options) @@ -108,30 +88,37 @@ public static class DLSiteToDomainMapper if (!options.Contains(languageCode)) return null; - if (!TranslationLanguageMap.TryGetValue(languageCode, out Language language)) + if (!SupportedLanguage.TryFromCode(languageCode, out SupportedLanguage? supportedLanguage)) return null; string originalId = translationInfo.OriginalWorkNumber; - bool isOfficial = options.Contains(OptOfficialTranslation); - bool isRecommended = options.Contains(OptRecommendedTranslation); - return new VoiceWorkTranslation - { - OriginalProductId = originalId, - Language = language, - IsOfficialTranslation = isOfficial, - IsRecommendedTranslation = isRecommended - }; + TranslationKind translationKind = TranslationKind.None; + + if (options.Contains(OptOfficialTranslation)) + translationKind |= TranslationKind.Official; + + if (options.Contains(OptRecommendedTranslation)) + translationKind |= TranslationKind.Recommended; + + //bool isOfficial = options.Contains(OptOfficialTranslation); + //bool isRecommended = options.Contains(OptRecommendedTranslation); + + return new VoiceWorkTranslation( + OriginalProductId: originalId, + Language: supportedLanguage.Language, + Kind: translationKind + ); } private static SupportedLanguage[] MapSupportedLanguages(HashSet options) { List languages = []; - foreach (var (code, language) in SupportedLanguageFlags2) + foreach (SupportedLanguage supportedLanguage in SupportedLanguage.All) { - if (options.Contains(code) && !languages.Contains(language)) - languages.Add(language); + if (options.Contains(supportedLanguage.Code) && !languages.Contains(supportedLanguage)) + languages.Add(supportedLanguage); } return [.. languages]; diff --git a/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilder.cs b/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilder.cs index bcf94f8..74c62e0 100644 --- a/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilder.cs +++ b/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilder.cs @@ -1,15 +1,15 @@ -using JSMR.Domain.ValueObjects; -using JSMR.Infrastructure.Common.Locales; +using JSMR.Application.Enums; +using JSMR.Domain.ValueObjects; namespace JSMR.Infrastructure.Scanning; -public class DLSiteSearchFilterBuilder +public sealed class DLSiteSearchFilterBuilder { private readonly List _optionsAnd = []; private readonly List _optionsNot = []; private readonly List _excludedMakers = []; - private ILocale _locale = new JapaneseLocale(); + private Locale _locale = Locale.Japanese; private void AddToOptionsAnd(string value) { @@ -27,7 +27,7 @@ public class DLSiteSearchFilterBuilder _optionsNot.Add(value); } - public DLSiteSearchFilterBuilder UseLocale(ILocale locale) + public DLSiteSearchFilterBuilder UseLocale(Locale locale) { _locale = locale; @@ -90,12 +90,12 @@ public class DLSiteSearchFilterBuilder public string BuildSearchQuery(int pageNumber, int pageSize) { - ILocale locale = _locale ?? new JapaneseLocale(); + var (localeAbbreviation, localeCode) = LocaleMap.Map[_locale]; using (var writer = new StringWriter()) { writer.Write($"https://www.dlsite.com/maniax/"); - writer.Write($"fsr/=/language/{locale.Abbreviation}/"); + writer.Write($"fsr/=/language/{localeAbbreviation}/"); writer.Write("sex_category[0]/male/"); writer.Write("ana_flg/all/"); @@ -129,7 +129,7 @@ public class DLSiteSearchFilterBuilder writer.Write($"per_page/{pageSize}/"); writer.Write($"page/{pageNumber}/"); writer.Write("show_type/1/"); - writer.Write($"?locale={locale.Code}"); + writer.Write($"?locale={localeCode}"); return writer.ToString(); } diff --git a/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilderExtensions.cs b/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilderExtensions.cs index 7ad942d..c41e30b 100644 --- a/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilderExtensions.cs +++ b/JSMR.Infrastructure/Scanning/DLSiteSearchFilterBuilderExtensions.cs @@ -1,6 +1,5 @@ -using JSMR.Domain.ValueObjects; -using JSMR.Infrastructure.Common.Locales; -using JSMR.Infrastructure.Common.SupportedLanguages; +using JSMR.Application.Enums; +using JSMR.Domain.ValueObjects; namespace JSMR.Infrastructure.Scanning; @@ -8,12 +7,12 @@ public static class DLSiteSearchFilterBuilderExtensions { public static DLSiteSearchFilterBuilder UseDefaultLocale(this DLSiteSearchFilterBuilder searchFilterBuilder) { - return searchFilterBuilder.UseLocale(new JapaneseLocale()); + return searchFilterBuilder.UseLocale(Locale.Japanese); } public static DLSiteSearchFilterBuilder UseEnglishLocale(this DLSiteSearchFilterBuilder searchFilterBuilder) { - return searchFilterBuilder.UseLocale(new EnglishLocale()); + return searchFilterBuilder.UseLocale(Locale.English); } public static DLSiteSearchFilterBuilder IncludeJapaneseSupportedLanguage(this DLSiteSearchFilterBuilder searchFilterBuilder) diff --git a/JSMR.Infrastructure/Scanning/EnglishVoiceWorksScanner.cs b/JSMR.Infrastructure/Scanning/EnglishVoiceWorksScanner.cs index 872fb1c..2d9e9f3 100644 --- a/JSMR.Infrastructure/Scanning/EnglishVoiceWorksScanner.cs +++ b/JSMR.Infrastructure/Scanning/EnglishVoiceWorksScanner.cs @@ -1,7 +1,5 @@ -using JSMR.Application.Common; +using JSMR.Application.Enums; using JSMR.Domain.ValueObjects; -using JSMR.Infrastructure.Common.Locales; -using JSMR.Infrastructure.Common.SupportedLanguages; using JSMR.Infrastructure.Http; using System.Globalization; using System.Text.RegularExpressions; @@ -16,7 +14,7 @@ public partial class EnglishVoiceWorksScanner(IHtmlLoader loader) : VoiceWorksSc [GeneratedRegex(@"^(Early|Middle|Late)\s(.*?)\s(\d{4})", RegexOptions.IgnoreCase, "en-US")] private static partial Regex EstimatedDateRegex(); - protected override ILocale Locale => new EnglishLocale(); + protected override Locale Locale => Locale.English; protected override SupportedLanguage[] SupportedLanguages => [ diff --git a/JSMR.Infrastructure/Scanning/JapaneseVoiceWorksScanner.cs b/JSMR.Infrastructure/Scanning/JapaneseVoiceWorksScanner.cs index fb3ec45..ac8982c 100644 --- a/JSMR.Infrastructure/Scanning/JapaneseVoiceWorksScanner.cs +++ b/JSMR.Infrastructure/Scanning/JapaneseVoiceWorksScanner.cs @@ -1,7 +1,5 @@ -using JSMR.Application.Common; +using JSMR.Application.Enums; using JSMR.Domain.ValueObjects; -using JSMR.Infrastructure.Common.Locales; -using JSMR.Infrastructure.Common.SupportedLanguages; using JSMR.Infrastructure.Http; using System.Text.RegularExpressions; @@ -15,7 +13,7 @@ public partial class JapaneseVoiceWorksScanner(IHtmlLoader loader) : VoiceWorksS [GeneratedRegex("販売日: (.*?)年(.*?)月(.*)日", RegexOptions.IgnoreCase, "en-US")] private static partial Regex SalesDateRegex(); - protected override ILocale Locale => new JapaneseLocale(); + protected override Locale Locale => Locale.Japanese; protected override SupportedLanguage[] SupportedLanguages => [ diff --git a/JSMR.Infrastructure/Scanning/LocaleMap.cs b/JSMR.Infrastructure/Scanning/LocaleMap.cs new file mode 100644 index 0000000..ce295bc --- /dev/null +++ b/JSMR.Infrastructure/Scanning/LocaleMap.cs @@ -0,0 +1,16 @@ +using JSMR.Application.Enums; + +namespace JSMR.Infrastructure.Scanning; + +internal class LocaleMap +{ + public static readonly IReadOnlyDictionary Map = + new Dictionary + { + { Locale.Japanese, ("jp", "ja_JP") }, + { Locale.English, ("en", "en_US") }, + { Locale.ChineseSimplified, ("zh-cn", "zh_CN") }, + { Locale.ChineseTraditional, ("zh-tw", "zh_TW") }, + { Locale.Korean, ("ko", "ko_KR") }, + }; +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Scanning/VoiceWorksScanner.cs b/JSMR.Infrastructure/Scanning/VoiceWorksScanner.cs index b39ad50..f24df70 100644 --- a/JSMR.Infrastructure/Scanning/VoiceWorksScanner.cs +++ b/JSMR.Infrastructure/Scanning/VoiceWorksScanner.cs @@ -1,9 +1,9 @@ using HtmlAgilityPack; +using JSMR.Application.Enums; using JSMR.Application.Scanning.Contracts; using JSMR.Application.Scanning.Ports; using JSMR.Domain.Enums; using JSMR.Domain.ValueObjects; -using JSMR.Infrastructure.Common.Locales; using JSMR.Infrastructure.Http; using JSMR.Infrastructure.Scanning.Models; using System.Globalization; @@ -13,7 +13,7 @@ namespace JSMR.Infrastructure.Scanning; public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksScanner { - protected abstract ILocale Locale { get; } + protected abstract Locale Locale { get; } protected abstract SupportedLanguage[] SupportedLanguages { get; } protected abstract DateOnly? GetEstimatedReleaseDate(string expectedDate); diff --git a/JSMR.Tests/Integrations/DLSite/DLSiteClientTests.cs b/JSMR.Tests/Integrations/DLSite/DLSiteClientTests.cs index b2699b6..dc74a30 100644 --- a/JSMR.Tests/Integrations/DLSite/DLSiteClientTests.cs +++ b/JSMR.Tests/Integrations/DLSite/DLSiteClientTests.cs @@ -39,7 +39,7 @@ public class DLSiteClientTests result["RJ01230163"].HasTrial.ShouldBeTrue(); result["RJ01230163"].HasDLPlay.ShouldBeTrue(); result["RJ01230163"].HasReviews.ShouldBeTrue(); - result["RJ01230163"].SupportedLanguages.Length.ShouldBe(1); + result["RJ01230163"].SupportedLanguages.Count.ShouldBe(1); result["RJ01230163"].SupportedLanguages.Select(x => x.Language).ShouldContain(Language.English); result["RJ01230163"].DownloadCount.ShouldBe(659); result["RJ01230163"].WishlistCount.ShouldBe(380); @@ -78,7 +78,7 @@ public class DLSiteClientTests voiceWorkDetails.Series.Identifier.ShouldBe("SE0001"); voiceWorkDetails.Series.Name.ShouldBe("Series 1"); - voiceWorkDetails.SupportedLanguages.Length.ShouldBe(1); + voiceWorkDetails.SupportedLanguages.Count.ShouldBe(1); voiceWorkDetails.SupportedLanguages[0].Language.ShouldBe(Language.Japanese); } } \ No newline at end of file