Implemented DLSiteClient.
This commit is contained in:
15
JSMR.Infrastructure/Integrations/Chobit/ChobitClient.cs
Normal file
15
JSMR.Infrastructure/Integrations/Chobit/ChobitClient.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using JSMR.Application.Integrations.Chobit.Models;
|
||||
using JSMR.Application.Integrations.Ports;
|
||||
using JSMR.Infrastructure.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.Chobit;
|
||||
|
||||
public class ChobitClient(HttpClient http, ILogger logger) : ApiClient(http, logger), IChobitClient
|
||||
{
|
||||
public Task<ChobitWorkResult> GetSampleInfoAsync(string[] productIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var url = $"api/v2/dlsite/embed?workno_list=${string.Join(",", productIds)}";
|
||||
return GetJsonAsync<ChobitWorkResult>(url, ct: cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace JSMR.Infrastructure.Integrations.Chobit.Models;
|
||||
|
||||
public class ChobitWorkResult : Dictionary<string, ChobitResult> { }
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.Chobit.Models;
|
||||
|
||||
public record ChobitResult
|
||||
{
|
||||
[JsonPropertyName("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
[JsonPropertyName("works")]
|
||||
public ChobitWork[] Works { get; set; } = [];
|
||||
}
|
||||
39
JSMR.Infrastructure/Integrations/Chobit/Models/ChobitWork.cs
Normal file
39
JSMR.Infrastructure/Integrations/Chobit/Models/ChobitWork.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.Chobit.Models;
|
||||
|
||||
public record ChobitWork
|
||||
{
|
||||
[JsonPropertyName("work_id")]
|
||||
public string? WorkId { get; set; }
|
||||
|
||||
[JsonPropertyName("dlsite_work_id")]
|
||||
public string? DLSiteWorkId { get; set; }
|
||||
|
||||
[JsonPropertyName("work_name")]
|
||||
public string? WorkName { get; set; }
|
||||
|
||||
[JsonPropertyName("work_name_kana")]
|
||||
public string? WorkNameKana { get; set; }
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
[JsonPropertyName("embed_url")]
|
||||
public string? EmbedURL { get; set; }
|
||||
|
||||
[JsonPropertyName("thumb")]
|
||||
public string? Thumb { get; set; }
|
||||
|
||||
[JsonPropertyName("mini_thumb")]
|
||||
public string? MiniThumb { get; set; }
|
||||
|
||||
[JsonPropertyName("file_type")]
|
||||
public string? FileType { get; set; }
|
||||
|
||||
[JsonPropertyName("embed_width")]
|
||||
public decimal EmbedWidth { get; set; }
|
||||
|
||||
[JsonPropertyName("embed_height")]
|
||||
public decimal EmbedHeight { get; set; }
|
||||
}
|
||||
28
JSMR.Infrastructure/Integrations/DLSite/DLSiteClient.cs
Normal file
28
JSMR.Infrastructure/Integrations/DLSite/DLSiteClient.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using JSMR.Application.Integrations.DLSite.Models;
|
||||
using JSMR.Application.Integrations.Ports;
|
||||
using JSMR.Infrastructure.Http;
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Mapping;
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite;
|
||||
|
||||
public class DLSiteClient(HttpClient http, ILogger logger) : ApiClient(http, logger), IDLSiteClient
|
||||
{
|
||||
public async Task<VoiceWorkDetailCollection> GetVoiceWorkDetailsAsync(string[] productIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (productIds.Length == 0)
|
||||
throw new Exception("No products to get product information.");
|
||||
|
||||
string productIdCollection = string.Join(",", productIds.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(productIdCollection))
|
||||
throw new Exception("Invalid product id(s).");
|
||||
|
||||
string url = $"maniax/product/info/ajax?product_id={productIdCollection}&cdn_cache_min=1";
|
||||
|
||||
var productInfoCollection = await GetJsonAsync<ProductInfoCollection>(url, ct: cancellationToken);
|
||||
|
||||
return DLSiteToDomainMapper.Map(productInfoCollection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using JSMR.Application.Integrations.Ports;
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite;
|
||||
|
||||
public static class DLSiteClientRegistration
|
||||
{
|
||||
//public static IServiceCollection AddDLSiteClient(this IServiceCollection services, Uri baseAddress)
|
||||
//{
|
||||
// // Build per-client JSON options with the DLSite-specific converters
|
||||
// var json = DLSiteJsonContext.CreateOptions();
|
||||
|
||||
// services.AddHttpClient<IDLSiteClient, DLSiteClient>(client =>
|
||||
// {
|
||||
// client.BaseAddress = baseAddress; // e.g., new Uri("https://www.dlsite.com/")
|
||||
// client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||
// client.Timeout = TimeSpan.FromSeconds(20);
|
||||
// })
|
||||
// .AddPolicyHandler(ResiliencePolicies.DefaultRetry()); // optional Polly
|
||||
|
||||
// // Provide the options to the client via DI
|
||||
// services.AddSingleton(json);
|
||||
|
||||
// return services;
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using JSMR.Application.Common;
|
||||
using JSMR.Application.Integrations.DLSite.Models;
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Mapping;
|
||||
|
||||
public static class DLSiteToDomainMapper
|
||||
{
|
||||
private const string OptTrial = "TRI";
|
||||
private const string OptDLPlay = "DLP";
|
||||
private const string OptReviews = "REV";
|
||||
private const string OptOfficialTranslation = "DOT";
|
||||
|
||||
private const string OptAIFull = "AIG";
|
||||
private const string OptAIPartial = "AIP";
|
||||
|
||||
private static readonly (string Code, Language Lang)[] SupportedLanguageFlags =
|
||||
[
|
||||
("JPN", Language.Japanese),
|
||||
("ENG", Language.English),
|
||||
("CHI", Language.ChineseTraditional),
|
||||
("CHI_HANT", Language.ChineseTraditional),
|
||||
("CHI_HANS", Language.ChineseSimplified)
|
||||
];
|
||||
|
||||
private static readonly Dictionary<string, Language> TranslationLanguageMap =
|
||||
SupportedLanguageFlags.ToDictionary(x => x.Code, x => x.Lang, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static VoiceWorkDetailCollection Map(ProductInfoCollection? productInfoCollection)
|
||||
{
|
||||
VoiceWorkDetailCollection result = [];
|
||||
|
||||
if (productInfoCollection is null)
|
||||
return result;
|
||||
|
||||
foreach (var keyValue in productInfoCollection)
|
||||
{
|
||||
string productId = keyValue.Key;
|
||||
ProductInfo productInfo = keyValue.Value;
|
||||
|
||||
VoiceWorkDetails voiceWorkDetails = Map(productInfo);
|
||||
result[productId] = voiceWorkDetails;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static VoiceWorkDetails Map(ProductInfo productInfo)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(productInfo);
|
||||
|
||||
string[] options = [.. productInfo.Options.Where(s => !string.IsNullOrWhiteSpace(s))];
|
||||
HashSet<string> optionsSet = new(options, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return new VoiceWorkDetails
|
||||
{
|
||||
Series = MapSeries(productInfo),
|
||||
Translation = MapTranslation(productInfo, optionsSet),
|
||||
WishlistCount = productInfo.WishlistCount,
|
||||
DownloadCount = productInfo.DownloadCount,
|
||||
RegistrationDate = productInfo.RegistrationDate,
|
||||
SupportedLanguages = MapSupportedLanguages(optionsSet),
|
||||
AI = MapAIGeneration(optionsSet),
|
||||
HasTrial = optionsSet.Contains(OptTrial),
|
||||
HasDLPlay = optionsSet.Contains(OptDLPlay),
|
||||
HasReviews = optionsSet.Contains(OptReviews)
|
||||
};
|
||||
}
|
||||
|
||||
private static VoiceWorkSeries? MapSeries(ProductInfo productInfo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(productInfo.TitleId) || string.IsNullOrWhiteSpace(productInfo.TitleName))
|
||||
return null;
|
||||
|
||||
return new VoiceWorkSeries
|
||||
{
|
||||
Identifier = productInfo.TitleId,
|
||||
Name = productInfo.TitleName
|
||||
};
|
||||
}
|
||||
|
||||
private static VoiceWorkTranslation? MapTranslation(ProductInfo info, HashSet<string> options)
|
||||
{
|
||||
ProductTranslationInfo? translationInfo = info.TranslationInfo;
|
||||
|
||||
if (translationInfo == null || string.IsNullOrWhiteSpace(translationInfo.OriginalWorkNumber) || string.IsNullOrWhiteSpace(translationInfo.Language))
|
||||
return null;
|
||||
|
||||
string languageCode = translationInfo.Language.Trim();
|
||||
|
||||
if (!options.Contains(languageCode))
|
||||
return null;
|
||||
|
||||
if (!TranslationLanguageMap.TryGetValue(languageCode, out Language language))
|
||||
return null;
|
||||
|
||||
string originalId = translationInfo.OriginalWorkNumber;
|
||||
bool isOfficial = options.Contains(OptOfficialTranslation);
|
||||
|
||||
return new VoiceWorkTranslation
|
||||
{
|
||||
OriginalProductId = originalId,
|
||||
Language = language,
|
||||
IsOfficialTranslation = isOfficial
|
||||
};
|
||||
}
|
||||
|
||||
private static Language[] MapSupportedLanguages(HashSet<string> options)
|
||||
{
|
||||
List<Language> languages = [];
|
||||
|
||||
foreach (var (code, language) in SupportedLanguageFlags)
|
||||
{
|
||||
if (options.Contains(code) && !languages.Contains(language))
|
||||
languages.Add(language);
|
||||
}
|
||||
|
||||
return [.. languages];
|
||||
}
|
||||
|
||||
private static AIGeneration MapAIGeneration(HashSet<string> options)
|
||||
{
|
||||
if (options.Contains(OptAIFull))
|
||||
return AIGeneration.Full;
|
||||
|
||||
if (options.Contains(OptAIPartial))
|
||||
return AIGeneration.Partial;
|
||||
|
||||
return AIGeneration.None;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class DownloadCountItem
|
||||
{
|
||||
[JsonPropertyName("workno")]
|
||||
public string? WorkNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("edition_id")]
|
||||
public int EditionId { get; set; }
|
||||
|
||||
[JsonPropertyName("edition_type")]
|
||||
public string? EditionType { get; set; }
|
||||
|
||||
[JsonPropertyName("display_order")]
|
||||
public int DisplayOrder { get; set; }
|
||||
|
||||
[JsonPropertyName("label")]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[JsonPropertyName("lang")]
|
||||
public string? Language { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_count")]
|
||||
[JsonConverter(typeof(NumberConverter))]
|
||||
public int DownloadCount { get; set; }
|
||||
|
||||
[JsonPropertyName("display_label")]
|
||||
public string? DisplayLabel { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductBonus
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonPropertyName("dist_flg")]
|
||||
public string? DistributionFlag { get; set; }
|
||||
|
||||
[JsonPropertyName("end_date")]
|
||||
[JsonConverter(typeof(NullableDateTimeConverterUsingDateTimeParse))]
|
||||
public DateTime? EndDate { get; set; }
|
||||
|
||||
[JsonPropertyName("end_date_str")]
|
||||
public string? EndDateString { get; set; }
|
||||
}
|
||||
245
JSMR.Infrastructure/Integrations/DLSite/Models/ProductInfo.cs
Normal file
245
JSMR.Infrastructure/Integrations/DLSite/Models/ProductInfo.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductInfo
|
||||
{
|
||||
[JsonPropertyName("site_id")]
|
||||
public string? SiteId { get; set; }
|
||||
|
||||
[JsonPropertyName("site_id_touch")]
|
||||
public string? SiteIdTouch { get; set; }
|
||||
|
||||
[JsonPropertyName("maker_id")]
|
||||
public string? MakerId { get; set; }
|
||||
|
||||
[JsonPropertyName("age_category")]
|
||||
public int AgeCategory { get; set; }
|
||||
|
||||
[JsonPropertyName("affiliate_deny")]
|
||||
public int AffiliateDeny { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_count")]
|
||||
[JsonConverter(typeof(NumberConverter))]
|
||||
public int DownloadCount { get; set; }
|
||||
|
||||
// This value can be a number or "false"
|
||||
[JsonPropertyName("wishlist_count")]
|
||||
[JsonConverter(typeof(NumberConverter))]
|
||||
public int WishlistCount { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_format")]
|
||||
public int DownloadFormat { get; set; }
|
||||
|
||||
[JsonPropertyName("rank")]
|
||||
public ProductRank[] Rank { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("rate_average")]
|
||||
public int? RateAverage { get; set; }
|
||||
|
||||
[JsonPropertyName("rate_average_2dp")]
|
||||
public decimal? RateAverage2DP { get; set; }
|
||||
|
||||
[JsonPropertyName("rate_average_star")]
|
||||
public int? RateAverageStar { get; set; }
|
||||
|
||||
[JsonPropertyName("rate_count")]
|
||||
public int? RateCount { get; set; }
|
||||
|
||||
[JsonPropertyName("rate_count_detail")]
|
||||
public ProductRateCountDetail[] RateCountDetail { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("review_count")]
|
||||
[JsonConverter(typeof(NumberConverter))]
|
||||
public int ReviewCount { get; set; }
|
||||
|
||||
[JsonPropertyName("price")]
|
||||
public int? Price { get; set; }
|
||||
|
||||
[JsonPropertyName("price_without_tax")]
|
||||
public int? PriceWithoutTax { get; set; }
|
||||
|
||||
[JsonPropertyName("price_str")]
|
||||
public string? PriceString { get; set; }
|
||||
|
||||
[JsonPropertyName("default_point_rate")]
|
||||
public int? DefaultPointRate { get; set; }
|
||||
|
||||
[JsonPropertyName("default_point")]
|
||||
public int DefaultPoint { get; set; }
|
||||
|
||||
[JsonPropertyName("product_point_rate")]
|
||||
public int? ProductPointRate { get; set; }
|
||||
|
||||
[JsonPropertyName("dlsiteplay_work")]
|
||||
public bool DLSitePlayWork { get; set; }
|
||||
|
||||
[JsonPropertyName("is_sale")]
|
||||
public bool IsSale { get; set; }
|
||||
|
||||
[JsonPropertyName("on_sale")]
|
||||
public int OnSale { get; set; }
|
||||
|
||||
[JsonPropertyName("is_discount")]
|
||||
public bool IsDiscount { get; set; }
|
||||
|
||||
[JsonPropertyName("is_pointup")]
|
||||
public bool IsPointUp { get; set; }
|
||||
|
||||
//[JsonPropertyName("gift")]
|
||||
//[JsonConverter(typeof(ListConverter<string>))]
|
||||
//public List<string> Gift { get; set; }
|
||||
|
||||
[JsonPropertyName("is_rental")]
|
||||
public bool IsRental { get; set; }
|
||||
|
||||
[JsonPropertyName("work_rentals")]
|
||||
public string[] WorkRentals { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("upgrade_min_price")]
|
||||
public int UpgradeMinPrice { get; set; }
|
||||
|
||||
[JsonPropertyName("down_url")]
|
||||
public string? DownloadURL { get; set; }
|
||||
|
||||
[JsonPropertyName("is_tartget")]
|
||||
public string? IsTargetGet { get; set; }
|
||||
|
||||
[JsonPropertyName("title_id")]
|
||||
public string? TitleId { get; set; }
|
||||
|
||||
[JsonPropertyName("title_name")]
|
||||
public string? TitleName { get; set; }
|
||||
|
||||
[JsonPropertyName("title_volumn")]
|
||||
public int? TitleVolumeNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("title_work_count")]
|
||||
public int? TitleWorkCount { get; set; }
|
||||
|
||||
[JsonPropertyName("is_title_completed")]
|
||||
public bool IsTitleCompleted { get; set; }
|
||||
|
||||
[JsonPropertyName("bulkbuy_key")]
|
||||
public string? BulkBuyKey { get; set; }
|
||||
|
||||
[JsonPropertyName("bonuses")]
|
||||
public ProductBonus[] Bonuses { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("is_limit_work")]
|
||||
public bool IsLimitWork { get; set; }
|
||||
|
||||
[JsonPropertyName("is_sold_out")]
|
||||
public bool IsSoldOut { get; set; }
|
||||
|
||||
[JsonPropertyName("limit_stock")]
|
||||
public int LimitStock { get; set; }
|
||||
|
||||
[JsonPropertyName("is_reserve_work")]
|
||||
public bool IsReserveWork { get; set; }
|
||||
|
||||
[JsonPropertyName("is_reservable")]
|
||||
public bool IsReservable { get; set; }
|
||||
|
||||
[JsonPropertyName("is_timesale")]
|
||||
public bool IsTimeSale { get; set; }
|
||||
|
||||
[JsonPropertyName("timesale_stock")]
|
||||
public int TimeSaleStock { get; set; }
|
||||
|
||||
[JsonPropertyName("is_free")]
|
||||
public bool IsFree { get; set; }
|
||||
|
||||
[JsonPropertyName("is_oly")]
|
||||
public bool IsOLY { get; set; }
|
||||
|
||||
[JsonPropertyName("is_led")]
|
||||
public bool IsLED { get; set; }
|
||||
|
||||
[JsonPropertyName("is_wcc")]
|
||||
public bool IsWCC { get; set; }
|
||||
|
||||
[JsonPropertyName("translation_info")]
|
||||
public ProductTranslationInfo? TranslationInfo { get; set; }
|
||||
|
||||
[JsonPropertyName("work_name")]
|
||||
public string? WorkName { get; set; }
|
||||
|
||||
[JsonPropertyName("work_image")]
|
||||
public string? WorkImage { get; set; }
|
||||
|
||||
[JsonPropertyName("sales_end_info")]
|
||||
public string? SalesEndInfo { get; set; }
|
||||
|
||||
[JsonPropertyName("voice_pack")]
|
||||
public string? VoicePack { get; set; }
|
||||
|
||||
[JsonPropertyName("regist_date")]
|
||||
[JsonConverter(typeof(NullableDateTimeConverterUsingDateTimeParse))]
|
||||
public DateTime? RegistrationDate { get; set; }
|
||||
|
||||
[JsonPropertyName("locale_price")]
|
||||
[JsonConverter(typeof(DictionaryConverter<string, decimal>))]
|
||||
public Dictionary<string, decimal> LocalePrice { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("locale_price_str")]
|
||||
[JsonConverter(typeof(DictionaryConverter<string, string>))]
|
||||
public Dictionary<string, string> LocalePriceString { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("work_type")]
|
||||
public string? WorkType { get; set; }
|
||||
|
||||
[JsonPropertyName("book_type")]
|
||||
public string? BookType { get; set; }
|
||||
|
||||
[JsonPropertyName("is_pack_work")]
|
||||
public bool IsPackWork { get; set; }
|
||||
|
||||
[JsonPropertyName("limited_free_terms")]
|
||||
public string[] LimitedFreeTerms { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("dl_count_total")]
|
||||
public int DownloadCountTotal { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_count_items")]
|
||||
public DownloadCountItem[] DownloadCountItems { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("discount_rate")]
|
||||
public int? DiscountRate { get; set; }
|
||||
|
||||
[JsonPropertyName("official_price")]
|
||||
public int? OfficialPrice { get; set; }
|
||||
|
||||
// Don't know the type for sure
|
||||
[JsonPropertyName("campaign_id")]
|
||||
public string? CampaignId { get; set; }
|
||||
|
||||
[JsonPropertyName("official_price_str")]
|
||||
public string? OfficialPriceString { get; set; }
|
||||
|
||||
[JsonPropertyName("locale_official_price")]
|
||||
[JsonConverter(typeof(DictionaryConverter<string, decimal>))]
|
||||
public Dictionary<string, decimal> LocaleOfficialPrice { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("locale_official_price_str")]
|
||||
[JsonConverter(typeof(DictionaryConverter<string, string>))]
|
||||
public Dictionary<string, string> LocaleOfficialPriceString { get; set; } = [];
|
||||
|
||||
// TODO: Convert to date
|
||||
[JsonPropertyName("discount_end_date")]
|
||||
public string? DiscountEndDate { get; set; }
|
||||
|
||||
[JsonPropertyName("discount_to")]
|
||||
public string? DiscountTo { get; set; }
|
||||
|
||||
[JsonPropertyName("discount_caption")]
|
||||
public string? DiscountCaption { get; set; }
|
||||
|
||||
[JsonPropertyName("default_point_str")]
|
||||
public string? DefaultPointString { get; set; }
|
||||
|
||||
[JsonPropertyName("options")]
|
||||
[JsonConverter(typeof(ProductInfoOptionsConverter))]
|
||||
public string[] Options { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductInfoCollection : Dictionary<string, ProductInfo>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductRank
|
||||
{
|
||||
[JsonPropertyName("term")]
|
||||
public string? Term { get; set; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string? Category { get; set; }
|
||||
|
||||
[JsonPropertyName("rank")]
|
||||
public int Rank { get; set; }
|
||||
|
||||
[JsonPropertyName("rank_date")]
|
||||
public string? RankDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductRateCountDetail
|
||||
{
|
||||
[JsonPropertyName("review_point")]
|
||||
public int ReviewPoint { get; set; }
|
||||
|
||||
[JsonPropertyName("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
[JsonPropertyName("ratio")]
|
||||
public int Ratio { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductReview
|
||||
{
|
||||
[JsonPropertyName("member_review_id")]
|
||||
public string? MemberReviewId { get; set; }
|
||||
|
||||
[JsonPropertyName("workno")]
|
||||
public string? WorkNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("reviewer_id")]
|
||||
public string? ReviewerId { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
[JsonPropertyName("recommend")]
|
||||
public string? Recommend { get; set; }
|
||||
|
||||
[JsonPropertyName("spoiler")]
|
||||
public string? Spoiler { get; set; }
|
||||
|
||||
[JsonPropertyName("review_title")]
|
||||
public string? ReviewTitle { get; set; }
|
||||
|
||||
[JsonPropertyName("review_text")]
|
||||
public string? ReviewText { get; set; }
|
||||
|
||||
[JsonPropertyName("entry_date")]
|
||||
[JsonConverter(typeof(DateTimeConverterUsingDateTimeParse))]
|
||||
public DateTime EntryDate { get; set; }
|
||||
|
||||
[JsonPropertyName("regist_date")]
|
||||
[JsonConverter(typeof(DateTimeConverterUsingDateTimeParse))]
|
||||
public DateTime RegistrationDate { get; set; }
|
||||
|
||||
[JsonPropertyName("good_review")]
|
||||
public string? GoodReview { get; set; }
|
||||
|
||||
[JsonPropertyName("bad_review")]
|
||||
public string? BadReview { get; set; }
|
||||
|
||||
[JsonPropertyName("circle_id")]
|
||||
public string? CircleId { get; set; }
|
||||
|
||||
[JsonPropertyName("nick_name")]
|
||||
public string? Nickname { get; set; }
|
||||
|
||||
[JsonPropertyName("popularity")]
|
||||
public string? Popularity { get; set; }
|
||||
|
||||
[JsonPropertyName("rate")]
|
||||
public string? Rate { get; set; }
|
||||
|
||||
[JsonPropertyName("circle_name")]
|
||||
public string? CircleName { get; set; }
|
||||
|
||||
[JsonPropertyName("reviewer_status")]
|
||||
public string? ReviewerStatus { get; set; }
|
||||
|
||||
[JsonPropertyName("is_purchased")]
|
||||
public string? IsPurchased { get; set; }
|
||||
|
||||
[JsonPropertyName("pickup")]
|
||||
public bool Pickup { get; set; }
|
||||
|
||||
[JsonPropertyName("rate_num")]
|
||||
public string? RateNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("reviewer_rank")]
|
||||
public string? ReviewerRank { get; set; }
|
||||
|
||||
[JsonPropertyName("longtext")]
|
||||
public bool LongText { get; set; }
|
||||
|
||||
[JsonPropertyName("genre")]
|
||||
public Dictionary<string, string> Genre { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductReviewCollection
|
||||
{
|
||||
[JsonPropertyName("is_success")]
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
[JsonPropertyName("error_msg")]
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
[JsonPropertyName("product_id")]
|
||||
public string? ProductId { get; set; }
|
||||
|
||||
[JsonPropertyName("product_name")]
|
||||
public string? ProductName { get; set; }
|
||||
|
||||
[JsonPropertyName("is_reserve")]
|
||||
public bool IsReserve { get; set; }
|
||||
|
||||
[JsonPropertyName("review_deny")]
|
||||
public bool ReviewDeny { get; set; }
|
||||
|
||||
[JsonPropertyName("mix_pickup")]
|
||||
public bool MixPickup { get; set; }
|
||||
|
||||
[JsonPropertyName("order")]
|
||||
public string? Order { get; set; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public string? Limit { get; set; }
|
||||
|
||||
[JsonPropertyName("page")]
|
||||
public int Page { get; set; }
|
||||
|
||||
[JsonPropertyName("review_list")]
|
||||
public ProductReview[] ReviewList { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("count")]
|
||||
public string? Count { get; set; }
|
||||
|
||||
[JsonPropertyName("reviewer_genre_list")]
|
||||
public ProductReviewerGenre[] ReviewerGenreList { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductReviewerGenre
|
||||
{
|
||||
[JsonPropertyName("genre")]
|
||||
public string? Genre { get; set; }
|
||||
|
||||
[JsonPropertyName("genre_count")]
|
||||
public string? GenreCount { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class ProductTranslationInfo
|
||||
{
|
||||
[JsonPropertyName("is_translation_agree")]
|
||||
public bool IsTranslationAgree { get; set; }
|
||||
|
||||
[JsonPropertyName("is_volunteer")]
|
||||
public bool IsVolunteer { get; set; }
|
||||
|
||||
[JsonPropertyName("is_original")]
|
||||
public bool IsOriginal { get; set; }
|
||||
|
||||
[JsonPropertyName("is_parent")]
|
||||
public bool IsParent { get; set; }
|
||||
|
||||
[JsonPropertyName("is_child")]
|
||||
public bool IsChild { get; set; }
|
||||
|
||||
[JsonPropertyName("original_workno")]
|
||||
public string? OriginalWorkNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("parent_workno")]
|
||||
public string? ParentWorkNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("child_worknos")]
|
||||
public string[] ChildWorkNumbers { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("lang")]
|
||||
public string? Language { get; set; }
|
||||
|
||||
[JsonPropertyName("production_trade_price_rate")]
|
||||
public int ProductionTradePriceRate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class RelatedProductSeries
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public RelatedProductSeriesData? Data { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
[JsonPropertyName("msg")]
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
|
||||
public class RelatedProductSeriesData
|
||||
{
|
||||
[JsonPropertyName("worknos")]
|
||||
public string[] WorkNumbers { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
|
||||
[JsonSerializable(typeof(ProductInfoCollection))]
|
||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||
public partial class DLSiteJsonContext : JsonSerializerContext
|
||||
{
|
||||
public static JsonSerializerOptions CreateOptions()
|
||||
{
|
||||
JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
|
||||
options.Converters.Add(new NumberConverter());
|
||||
options.Converters.Add(new DictionaryConverter<string, decimal>());
|
||||
options.Converters.Add(new DictionaryConverter<string, string>());
|
||||
options.Converters.Add(new DateTimeConverterUsingDateTimeParse());
|
||||
options.Converters.Add(new NullableDateTimeConverterUsingDateTimeParse());
|
||||
options.Converters.Add(new ProductInfoOptionsConverter());
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
|
||||
public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
|
||||
{
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return DateTime.Parse(reader.GetString() ?? string.Empty);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
|
||||
public sealed class DictionaryConverter<TKey, TValue> : JsonConverter<Dictionary<TKey, TValue>> where TKey : notnull
|
||||
{
|
||||
public override Dictionary<TKey, TValue>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(ref reader, options);
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Dictionary<TKey, TValue> value, JsonSerializerOptions options)
|
||||
=> JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
|
||||
public sealed class NullableDateTimeConverterUsingDateTimeParse : JsonConverter<DateTime?>
|
||||
{
|
||||
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType is JsonTokenType.Null)
|
||||
return null;
|
||||
|
||||
if (reader.TokenType is JsonTokenType.String)
|
||||
{
|
||||
var s = reader.GetString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
||||
if (DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var dt))
|
||||
return dt;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is DateTime dt)
|
||||
writer.WriteStringValue(dt);
|
||||
else
|
||||
writer.WriteNullValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
|
||||
public sealed class NumberConverter : JsonConverter<int>
|
||||
{
|
||||
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.Number => reader.TryGetInt32(out var i) ? i : 0,
|
||||
JsonTokenType.String => int.TryParse(reader.GetString(), out var j) ? j : 0,
|
||||
JsonTokenType.False => 0,
|
||||
JsonTokenType.True => 1,
|
||||
JsonTokenType.Null => 0,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
|
||||
=> writer.WriteNumberValue(value);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JSMR.Infrastructure.Integrations.DLSite.Serialization;
|
||||
|
||||
public sealed class ProductInfoOptionsConverter : JsonConverter<string[]>
|
||||
{
|
||||
public override string[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string value = reader.GetString() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return [];
|
||||
|
||||
return [.. value.Split('#')];
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, string[] value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(string.Join("#", value));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user