Updated scanner tests to work with user english locale pages.

This commit is contained in:
2025-10-17 19:47:15 -04:00
parent 278b6df650
commit dbe249194a
9 changed files with 470 additions and 63 deletions

View File

@@ -13,7 +13,8 @@ public class DLSiteHtmlDocument
public DLSiteHtmlDocument(HtmlDocument document) public DLSiteHtmlDocument(HtmlDocument document)
{ {
_workColumns = document.DocumentNode.SelectNodes("//dl[@class='work_1col']"); _workColumns = document.DocumentNode.SelectNodes("//dl[@class='work_1col']");
_workColumnRights = document.DocumentNode.SelectNodes("//td[@class='work_1col_right']"); //_workColumnRights = document.DocumentNode.SelectNodes("//td[@class='work_1col_right']");
_workColumnRights = document.DocumentNode.SelectNodes("//td[contains(@class, 'work_1col_right')]");
_workThumbs = document.DocumentNode.SelectNodes("//div[@class='work_thumb']"); _workThumbs = document.DocumentNode.SelectNodes("//div[@class='work_thumb']");
PageTotalNode = document.DocumentNode.SelectNodes("//div[@class='page_total']/strong")[0]; PageTotalNode = document.DocumentNode.SelectNodes("//div[@class='page_total']/strong")[0];

View File

@@ -11,17 +11,18 @@ public class DLSiteHtmlNode
public HtmlNode ProductNode { get; private set; } public HtmlNode ProductNode { get; private set; }
public HtmlNode ProductLinkNode { get; private set; } public HtmlNode ProductLinkNode { get; private set; }
public HtmlNode ProductTextNode { get; private set; } public HtmlNode ProductTextNode { get; private set; }
public HtmlNode DescriptionNode { get; private set; } public HtmlNode? DescriptionNode { get; private set; }
public HtmlNode MakerNode { get; private set; } public HtmlNode MakerNode { get; private set; }
public HtmlNode MakerLinkNode { get; private set; } public HtmlNode MakerLinkNode { get; private set; }
public HtmlNode SalesDateNode { get; private set; } public HtmlNode? ExpectedDateNode { get; private set; }
public HtmlNode ExpectedDateNode { get; private set; } public HtmlNode? WorkInfoBox { get; private set; }
public HtmlNode? SalesDateNode { get; private set; }
public HtmlNode DownloadsNode { get; private set; } public HtmlNode DownloadsNode { get; private set; }
public HtmlNode StarRatingNode { get; private set; } public HtmlNode? StarRatingNode { get; private set; }
public HtmlNode ImageNode { get; private set; } public HtmlNode ImageNode { get; private set; }
public List<HtmlNode> GenreNodes { get; private set; } public HtmlNode[] GenreNodes { get; private set; }
public List<HtmlNode> SearchTagNodes { get; private set; } public HtmlNode[] SearchTagNodes { get; private set; }
public List<HtmlNode> CreatorNodes { get; private set; } public HtmlNode[] CreatorNodes { get; private set; }
public DLSiteHtmlNode(HtmlNode leftNode, HtmlNode rightNode, HtmlNode thumbNode) public DLSiteHtmlNode(HtmlNode leftNode, HtmlNode rightNode, HtmlNode thumbNode)
{ {
@@ -33,35 +34,44 @@ public class DLSiteHtmlNode
ProductLinkNode = ProductNode.SelectNodes(".//a")[0]; ProductLinkNode = ProductNode.SelectNodes(".//a")[0];
ProductTextNode = GetProductTextNode(); ProductTextNode = GetProductTextNode();
DescriptionNode = LeftNode.SelectNodes(".//dd[@class='work_text']")[0]; //DescriptionNode = LeftNode.SelectNodes(".//dd[@class='work_text']")[0];
DescriptionNode = LeftNode.SelectNodes(".//dd[@class='work_text']")?.FirstOrDefault();
MakerNode = LeftNode.SelectNodes(".//dd[@class='maker_name']")[0]; MakerNode = LeftNode.SelectNodes(".//dd[@class='maker_name']")[0];
MakerLinkNode = MakerNode.SelectNodes(".//a[contains(@href, 'maker_id')]")[0]; MakerLinkNode = MakerNode.SelectNodes(".//a[contains(@href, 'maker_id')]")[0];
ExpectedDateNode = GetExpectedDateNode(); //ExpectedDateNode = GetExpectedDateNode();
ExpectedDateNode = ProductNode.SelectNodes(".//p[@class='expected_date']")?.FirstOrDefault();
InitializeGenreNodes(); GenreNodes = GetGenreNodes();
InitializeSearchTagNodes(); SearchTagNodes = GetSearchTagNodes();
InitializeCreatorNodes(); CreatorNodes = GetCreatorNodes();
InitializeSalesAndDownloadsNodes(); WorkInfoBox = RightNode.SelectNodes(".//ul[@class='work_info_box']")?.FirstOrDefault();
InitializeStarRatingNode(); SalesDateNode = WorkInfoBox?.SelectNodes(".//li[@class='sales_date']")?.FirstOrDefault();
InitializeImageNode();
// TODO: Fix!
//DownloadsNode = RightNode.SelectSingleNode(".//span[@class='_dl_count_" + works[rightsIndex].ProductId + "']");
DownloadsNode = RightNode.SelectSingleNode(".//span[contains(@class, '_dl_count_')]");
//InitializeSalesAndDownloadsNodes();
StarRatingNode = GetStarRatingNode();
ImageNode = GetImageNode();
} }
private void InitializeGenreNodes() private HtmlNode[] GetGenreNodes()
{ {
HtmlNode genreNode = LeftNode.SelectNodes(".//dd[@class='work_genre']")[0]; HtmlNode genreNode = LeftNode.SelectNodes(".//dd[@class='work_genre']")[0];
GenreNodes = [.. genreNode.SelectNodes(".//span")]; return [.. genreNode.SelectNodes(".//span")];
} }
private void InitializeSearchTagNodes() private HtmlNode[] GetSearchTagNodes()
{ {
HtmlNodeCollection searchTagNodes = LeftNode.SelectNodes(".//dd[@class='search_tag']"); HtmlNodeCollection searchTagNodes = LeftNode.SelectNodes(".//dd[@class='search_tag']");
if (searchTagNodes == null || searchTagNodes.Count == 0) if (searchTagNodes == null || searchTagNodes.Count == 0)
{ {
SearchTagNodes = []; return [];
} }
else else
{ {
@@ -69,56 +79,64 @@ public class DLSiteHtmlNode
if (searchTagNodesLinks == null || searchTagNodesLinks.Count == 0) if (searchTagNodesLinks == null || searchTagNodesLinks.Count == 0)
{ {
SearchTagNodes = []; return [];
} }
else else
{ {
SearchTagNodes = [.. searchTagNodesLinks]; return [.. searchTagNodesLinks];
} }
} }
} }
private void InitializeCreatorNodes() private HtmlNode[] GetCreatorNodes()
{ {
HtmlNodeCollection creatorNodes = MakerNode.SelectNodes(".//a[contains(@href, 'keyword_creater')]"); HtmlNodeCollection creatorNodes = MakerNode.SelectNodes(".//a[contains(@href, 'keyword_creater')]");
if (creatorNodes == null || creatorNodes.Count == 0) if (creatorNodes == null || creatorNodes.Count == 0)
{ {
CreatorNodes = []; return [];
} }
else else
{ {
CreatorNodes = [.. creatorNodes]; return [.. creatorNodes];
} }
} }
private void InitializeSalesAndDownloadsNodes() //private void InitializeSalesAndDownloadsNodes()
{ //{
HtmlNodeCollection workInfoBox = RightNode.SelectNodes(".//ul[@class='work_info_box']"); // HtmlNodeCollection workInfoBox = RightNode.SelectNodes(".//ul[@class='work_info_box']");
if (workInfoBox != null) // if (workInfoBox != null)
{ // {
HtmlNodeCollection salesDateNodes = workInfoBox[0].SelectNodes(".//li[@class='sales_date']"); // HtmlNodeCollection salesDateNodes = workInfoBox[0].SelectNodes(".//li[@class='sales_date']");
if (salesDateNodes != null && salesDateNodes.Count > 0) // if (salesDateNodes != null && salesDateNodes.Count > 0)
{ // {
SalesDateNode = salesDateNodes[0]; // SalesDateNode = salesDateNodes[0];
} // }
// TODO: Fix! // // TODO: Fix!
//DownloadsNode = RightNode.SelectSingleNode(".//span[@class='_dl_count_" + works[rightsIndex].ProductId + "']"); // //DownloadsNode = RightNode.SelectSingleNode(".//span[@class='_dl_count_" + works[rightsIndex].ProductId + "']");
DownloadsNode = RightNode.SelectSingleNode(".//span[contains(@class, '_dl_count_')]"); // DownloadsNode = RightNode.SelectSingleNode(".//span[contains(@class, '_dl_count_')]");
} // }
} //}
private void InitializeStarRatingNode() //private HtmlNode? GetSalesDateNode()
//{
// if (WorkInfoBox is null)
// return null;
// return WorkInfoBox.SelectNodes(".//li[@class='sales_date']").FirstOrDefault();
//}
private HtmlNode? GetStarRatingNode()
{ {
var ratingsNode = RightNode.SelectSingleNode(".//li[@class='work_rating']"); var ratingsNode = RightNode.SelectSingleNode(".//li[@class='work_rating']");
if (ratingsNode == null) if (ratingsNode == null)
return; return null;
StarRatingNode = ratingsNode.SelectSingleNode(".//div[contains(@class, 'star_rating')]"); return ratingsNode.SelectSingleNode(".//div[contains(@class, 'star_rating')]");
} }
private HtmlNode GetProductTextNode() private HtmlNode GetProductTextNode()
@@ -133,24 +151,24 @@ public class DLSiteHtmlNode
} }
} }
private HtmlNode GetExpectedDateNode() //private HtmlNode? GetExpectedDateNode()
{ //{
HtmlNodeCollection expectedDateNodes = ProductNode.SelectNodes(".//p[@class='expected_date']"); // HtmlNodeCollection expectedDateNodes = ProductNode.SelectNodes(".//p[@class='expected_date']").FirstOrDefault();
if (expectedDateNodes != null && expectedDateNodes.Count > 0) // if (expectedDateNodes != null && expectedDateNodes.Count > 0)
{ // {
return expectedDateNodes[0]; // return expectedDateNodes[0];
} // }
else // else
{ // {
return null; // return null;
} // }
} //}
private void InitializeImageNode() private HtmlNode GetImageNode()
{ {
HtmlNode linkNode = ThumbNode.SelectNodes(".//a")[0]; HtmlNode linkNode = ThumbNode.SelectNodes(".//a")[0];
ImageNode = linkNode.SelectNodes(".//img")[0]; return linkNode.SelectNodes(".//img")[0];
} }
} }

View File

@@ -5,7 +5,7 @@ namespace JSMR.Infrastructure.Scanning;
public static class ScannerUtilities public static class ScannerUtilities
{ {
public static List<string> GetStringListFromNodes(List<HtmlNode> nodes) public static List<string> GetStringListFromNodes(HtmlNode[] nodes)
{ {
return nodes return nodes
.Where(node => string.IsNullOrEmpty(node.InnerHtml) == false) .Where(node => string.IsNullOrEmpty(node.InnerHtml) == false)

View File

@@ -114,7 +114,7 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
return work; return work;
} }
private static AgeRating GetAgeRating(List<HtmlNode> genreNodes) private static AgeRating GetAgeRating(HtmlNode[] genreNodes)
{ {
List<string> genres = ScannerUtilities.GetStringListFromNodes(genreNodes); List<string> genres = ScannerUtilities.GetStringListFromNodes(genreNodes);

View File

@@ -39,7 +39,8 @@ public class DLSiteClientTests
result["RJ01230163"].HasTrial.ShouldBeTrue(); result["RJ01230163"].HasTrial.ShouldBeTrue();
result["RJ01230163"].HasDLPlay.ShouldBeTrue(); result["RJ01230163"].HasDLPlay.ShouldBeTrue();
result["RJ01230163"].HasReviews.ShouldBeTrue(); result["RJ01230163"].HasReviews.ShouldBeTrue();
result["RJ01230163"].SupportedLanguages.ShouldBe([Language.English]); result["RJ01230163"].SupportedLanguages.Length.ShouldBe(1);
result["RJ01230163"].SupportedLanguages.Select(x => x.Language).ShouldContain(Language.English);
result["RJ01230163"].DownloadCount.ShouldBe(659); result["RJ01230163"].DownloadCount.ShouldBe(659);
result["RJ01230163"].WishlistCount.ShouldBe(380); result["RJ01230163"].WishlistCount.ShouldBe(380);
} }
@@ -71,13 +72,13 @@ public class DLSiteClientTests
voiceWorkDetails.DownloadCount.ShouldBe(100); voiceWorkDetails.DownloadCount.ShouldBe(100);
voiceWorkDetails.HasTrial.ShouldBe(true); voiceWorkDetails.HasTrial.ShouldBe(true);
voiceWorkDetails.HasDLPlay.ShouldBe(true); voiceWorkDetails.HasDLPlay.ShouldBe(true);
voiceWorkDetails.AI.ShouldBe(Application.Common.AIGeneration.None); voiceWorkDetails.AI.ShouldBe(AIGeneration.None);
voiceWorkDetails.Series.ShouldNotBeNull(); voiceWorkDetails.Series.ShouldNotBeNull();
voiceWorkDetails.Series.Identifier.ShouldBe("SE0001"); voiceWorkDetails.Series.Identifier.ShouldBe("SE0001");
voiceWorkDetails.Series.Name.ShouldBe("Series 1"); voiceWorkDetails.Series.Name.ShouldBe("Series 1");
voiceWorkDetails.SupportedLanguages.Length.ShouldBe(1); voiceWorkDetails.SupportedLanguages.Length.ShouldBe(1);
voiceWorkDetails.SupportedLanguages[0].ShouldBe(Application.Common.Language.Japanese); voiceWorkDetails.SupportedLanguages[0].Language.ShouldBe(Language.Japanese);
} }
} }

View File

@@ -14,6 +14,8 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Integrations\DLSite\Product-Info.json" /> <EmbeddedResource Include="Integrations\DLSite\Product-Info.json" />
<EmbeddedResource Include="Scanning\English-Page-Updated.html" />
<EmbeddedResource Include="Scanning\Japanese-Page.html" />
<EmbeddedResource Include="Scanning\English-Page.html" /> <EmbeddedResource Include="Scanning\English-Page.html" />
</ItemGroup> </ItemGroup>

View File

@@ -0,0 +1,150 @@
<html>
<body>
<div data-toggle="found" class="sort_box border_b pb10">
<div class="status_select">
Reorder&nbsp;:
<select name="order" id="query_order" class="_change_submit"><!----><!----><option value="trend">Sort by popularity</option><option value="release_d">Release date - New to Old</option><option value="release">Release date - Old to New</option><option value="dl_d">Best Selling</option><option value="price">Price - Low to High</option><option value="price_d">Price - High to Low</option><option value="rate_d">Rating - High to Low</option><option value="review_d">Reviews - High to Low</option></select>
</div>
<div class="page_total">
<strong>626609</strong>
<span> total. Showing: </span>
<strong>130</strong>
</div>
<div class="display_type_select">
<span>Display&nbsp;</span>
<ul>
<li class="on display_normal">
<a>1カラム表示</a>
</li>
<li class="display_block">
<a href="#">画像表示</a>
</li>
</ul>
</div>
<div class="display_num_select">
<ul>
<li class="on">
<a>30</a>
</li>
<li class="">
<a href="#">50</a>
</li>
<li class="">
<a href="#">100</a>
</li>
</ul>
<span>Items per page&nbsp;</span>
</div>
</div>
<div id="search_result_list">
<table class="work_1col_table n_worklist">
<tbody>
<tr data-list_item_product_id="RJ01455722" class="bg_trans ">
<td class="work_1col_thumb is_overseas">
<div class="work_thumb">
<a href="https://www.dlsite.com/maniax/work/=/product_id/RJ01455722.html" id="_link_RJ01455722" class="work_thumb_inner"><img alt="[ENG Sub] Welcome to Soleil! [Translators Unite]" data-src="//img.dlsite.jp/modpub/images2/work/doujin/RJ325000/RJ324248_img_main.jpg" class="lazy" src="//img.dlsite.jp/modpub/images2/work/doujin/RJ325000/RJ324248_img_main.jpg" lazy="loaded"> <div class="work_img_popover"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ325000/RJ324248_img_main.jpg" alt="[ENG Sub] Welcome to Soleil! [Translators Unite]"></div></a>
<div class="work_category type_SOU"><a href="https://www.dlsite.com/maniax/fsr/=/work_type/SOU">Voice / ASMR</a></div>
</div>
</td>
<td class="is_overseas">
<dl class="work_1col">
<dt class="work_name">
<span class="period_date">Oct. 30, 13:59 (JST) Discounted for a limited time.</span>
<div class="icon_wrap">
</div>
<a href="https://www.dlsite.com/maniax/work/=/product_id/RJ01455722.html" title="[ENG Sub] Welcome to Soleil!">
[ENG Sub] Welcome to Soleil!
</a>
</dt>
<dd class="maker_name">
<a href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG60289.html">Translators Unite</a>
<span class="separator">/</span>
<span class="author"><a href="https://www.dlsite.com/home/fsr/=/keyword_creater/%22%E6%B2%BC%E5%80%89%E6%84%9B%E7%BE%8E%22" class="">沼倉愛美</a></span>
</dd>
<dd class="work_price_wrap">
<span class="work_price ">
<span class="work_price_parts"><span class="work_price_prefix">$</span> <span class="work_price_base">5.90</span> <span class="work_price_suffix"></span></span>
</span>
<span class="strike ">
<span class="work_price_parts"><span class="work_price_prefix">$</span> <span class="work_price_base">13.10</span> <span class="work_price_suffix"></span></span>
</span>
<span class="icon_campaign type_sale">55%OFF</span>
</dd>
<dd class="work_genre">
<span class="icon_GEN" title="All Ages">All Ages</span><span class="icon_TRI" title="Trial version">Trial version</span>
<span style="display: none;"></span>
<input type="hidden" class="__product_attributes" name="__product_attributes" id="_RJ01455722" value="RG60289,male,SOU,SND,DLP,REV,TRI,ENG,497,496,051,056,442,503,008,013" disabled="disabled">
</dd>
<dd class="search_tag">
<a href="https://www.dlsite.com/maniax/fsr/=/genre/051/from/work.genre">Moe</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/056/from/work.genre">Healing</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/496/from/work.genre">Binaural</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/497/from/work.genre">ASMR</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/442/from/work.genre">Ear Cleaning</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/008/from/work.genre">Slice of Life / Daily Living</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/013/from/work.genre">Heartwarming</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/503/from/work.genre">Whispering</a>
</dd>
</dl>
</td>
<td class="work_1col_right is_overseas">
<ul class="work_info_box">
<li class="sales_date">Release date:&nbsp;Oct/16/2025</li>
<li class="work_dl clear">
<div class="_work_dl_RJ01455722">Purchased:&nbsp;<span class="_dl_count_RJ01455722">1</span></div>
</li>
<li class="work_review clear">
</li>
<li class="work_rating"><div class="star_rating star_50">(3,420)</div></li>
</ul>
<div class="work_btn_box"><div hidden="hidden" class="ga4_event_item_RJ01455722" data-product_id="RJ01455722" data-work_name="[ENG Sub] Welcome to Soleil!" data-maker_id="RG60289" data-work_type="SOU" data-options="SND#DLP#REV#TRI#ENG" data-price="891" data-official_price="1980"></div> <ul class="work_btn_list btn_free_sample"><li class="work_btn_list_item"><p class="work_cart_xs"><a href="/maniax/cart/=/product_id/RJ01455722.html" class="btn_cart disabled">Add to Cart</a></p></li> <li class="work_btn_list_item"><p class="work_favorite_xs"><a href="/maniax/mypage/wishlist/=/product_id/RJ01455722.html" class="btn_favorite disabled">Favorites</a></p></li> <li class="work_btn_list_item"><li><p class="work_free_sample"><a href="#" class="btn_free_sample disabled _sample_none" data-product-id="RJ01455722">Samples</a></p></li></li></ul> <!----></div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,157 @@
<html>
<body>
<div data-toggle="found" class="sort_box border_b pb10">
<div class="status_select">
Reorder&nbsp;:
<select name="order" id="query_order" class="_change_submit"><!----><!----><option value="trend">Sort by popularity</option><option value="release_d">Release date - New to Old</option><option value="release">Release date - Old to New</option><option value="dl_d">Best Selling</option><option value="price">Price - Low to High</option><option value="price_d">Price - High to Low</option><option value="rate_d">Rating - High to Low</option><option value="review_d">Reviews - High to Low</option></select>
</div>
<div class="page_total">
<strong>626609</strong>
<span> total. Showing: </span>
<strong>130</strong>
</div>
<div class="display_type_select">
<span>Display&nbsp;</span>
<ul>
<li class="on display_normal">
<a>1カラム表示</a>
</li>
<li class="display_block">
<a href="#">画像表示</a>
</li>
</ul>
</div>
<div class="display_num_select">
<ul>
<li class="on">
<a>30</a>
</li>
<li class="">
<a href="#">50</a>
</li>
<li class="">
<a href="#">100</a>
</li>
</ul>
<span>Items per page&nbsp;</span>
</div>
</div>
<div id="search_result_list">
<table class="work_1col_table n_worklist">
<tbody>
<!-- RJ00000001 -->
<tr data-list_item_product_id="RJ01462066" class=" ">
<td class="work_1col_thumb ">
<div class="work_thumb">
<a href="https://www.dlsite.com/maniax/work/=/product_id/RJ01462066.html" id="_link_RJ01462066" class="work_thumb_inner"><img alt="小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術 [シルトクレーテ]" data-src="//img.dlsite.jp/resize/images2/work/doujin/RJ01463000/RJ01462066_img_main_240x240.jpg" class="lazy" src="//img.dlsite.jp/resize/images2/work/doujin/RJ01463000/RJ01462066_img_main_240x240.jpg" lazy="loaded"> <div class="work_img_popover"><img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" alt="小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術 [シルトクレーテ]"></div></a>
<div class="work_category type_SOU"><a href="https://www.dlsite.com/maniax/fsr/=/work_type/SOU">ボイス・ASMR</a></div>
</div>
</td>
<td class="">
<dl class="work_1col">
<dt class="work_name">
<span class="period_date">2025年11月11日 23時59分 割引終了</span>
<div class="icon_wrap">
<span class="icon_lead_01 type_exclusive" title="専売">専売</span>
</div>
<a href="https://www.dlsite.com/maniax/work/=/product_id/RJ01462066.html" title="小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術">
小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術
</a>
</dt>
<dd class="maker_name">
<a href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG40741.html">シルトクレーテ</a>
<span class="separator">/</span>
<span class="author"><a href="https://www.dlsite.com/maniax/fsr/=/keyword_creater/%22%E6%9F%9A%E6%9C%A8%E3%81%A4%E3%81%B0%E3%82%81%22" class="">柚木つばめ</a></span>
</dd>
<dd class="work_price_wrap">
<span class="work_price ">
<span class="work_price_parts"><span class="work_price_prefix">$</span> <span class="work_price_base">8.74</span> <span class="work_price_suffix"></span></span>
</span>
<span class="strike ">
<span class="work_price_parts"><span class="work_price_prefix">$</span> <span class="work_price_base">10.92</span> <span class="work_price_suffix"></span></span>
</span>
<span class="icon_campaign type_sale">20%OFF</span>
</dd>
<dd class="work_text">リピート指名していたオキニ嬢の在籍店が閉店し途方に暮れていた貴方だったが、ある日彼女の個人SNSで別のお店のリンクを発見する。大慌てで当日夜の予約を確保し、今度こそ抜いてくれないかと淡い期待を胸に駅近くのマンションの一室へ…。</dd>
<dd class="work_genre">
<span class="icon_TRI" title="体験版">体験版</span>
<span style="display: none;"></span>
<input type="hidden" class="__product_attributes" name="__product_attributes" id="_RJ01462066" value="RG40741,adl,male,SOU,JPN,SND,TRI,DLP,REV,496,124,125,122,144,152,523,528" disabled="disabled">
</dd>
<dd class="search_tag">
<a href="https://www.dlsite.com/maniax/fsr/=/genre/496/from/work.genre">バイノーラル/ダミヘ</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/124/from/work.genre">手コキ</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/125/from/work.genre">足コキ</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/122/from/work.genre">パイズリ</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/144/from/work.genre">言葉責め</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/152/from/work.genre">焦らし</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/523/from/work.genre">乳首責め</a>
<a href="https://www.dlsite.com/maniax/fsr/=/genre/528/from/work.genre">本番なし</a>
</dd>
</dl>
</td>
<td class="work_1col_right ">
<ul class="work_info_box">
<li class="sales_date">販売日:&nbsp;2025年10月15日</li>
<li class="work_dl clear">
<div class="_work_dl_RJ01462066">販売数:&nbsp;<span class="_dl_count_RJ01462066">1,220</span></div>
</li>
<li class="work_review clear">
<span class="work_to_review">
<div title="レビューあり">
<a href="https://www.dlsite.com/maniax/work/reviewlist/=/product_id/RJ01462066.html">(2)</a>
</div>
</span>
</li>
<li class="work_rating"><div class="star_rating star_50">(31)</div></li>
</ul>
<div class="work_btn_box"><div hidden="hidden" class="ga4_event_item_RJ01462066" data-product_id="RJ01462066" data-work_name="小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術" data-maker_id="RG40741" data-work_type="SOU" data-options="JPN#SND#TRI#DLP#REV" data-price="1320" data-official_price="1650"></div> <ul class="work_btn_list btn_free_sample"><li class="work_btn_list_item"><p class="work_cart_xs"><a href="/maniax/cart/=/product_id/RJ01462066.html" class="btn_cart">カートに入れる</a></p></li> <li class="work_btn_list_item"><p class="work_favorite_xs"><a href="/maniax/mypage/wishlist/=/product_id/RJ01462066.html" class="btn_favorite">お気に入りに追加</a></p></li> <li class="work_btn_list_item"><li><p class="work_free_sample"><a href="#" class="btn_free_sample" data-product-id="RJ01462066">無料サンプル</a></p></li></li></ul> <!----></div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -5,7 +5,7 @@ using JSMR.Tests.Utilities;
using NSubstitute; using NSubstitute;
using Shouldly; using Shouldly;
namespace JSMR.Tests.Integrations.DLSite; namespace JSMR.Tests.Scanning;
public class VoiceWorkScannerTests public class VoiceWorkScannerTests
{ {
@@ -14,6 +14,45 @@ public class VoiceWorkScannerTests
return await ResourceHelper.ReadAsync($"JSMR.Tests.Scanning.{resourceName}"); return await ResourceHelper.ReadAsync($"JSMR.Tests.Scanning.{resourceName}");
} }
[Fact]
public async Task Scan_With_Japanese_Locale()
{
string html = await ReadResourceAsync("Japanese-Page.html");
IHttpService httpService = Substitute.For<IHttpService>();
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
.Returns(Task.FromResult(html));
HtmlLoader loader = new(httpService);
JapaneseVoiceWorksScanner scanner = new(loader);
VoiceWorkScanOptions options = new(
PageNumber: 1,
PageSize: 100,
ExcludeAIGeneratedWorks: true,
ExcludePartiallyAIGeneratedWorks: true,
ExcludedMakerIds: []
);
var result = await scanner.ScanPageAsync(options, CancellationToken.None);
result.Count.ShouldBe(1);
result[0].ExpectedDate.ShouldBeNull();
result[0].SalesDate.ShouldBe(new DateOnly(2025, 10, 15));
result[0].ProductId.ShouldBe("RJ01462066");
result[0].ProductName.ShouldBe("小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術");
result[0].Description.ShouldBe("リピート指名していたオキニ嬢の在籍店が閉店し途方に暮れていた貴方だったが、ある日彼女の個人SNSで別のお店のリンクを発見する。大慌てで当日夜の予約を確保し、今度こそ抜いてくれないかと淡い期待を胸に駅近くのマンションの一室へ…。");
result[0].Maker.ShouldBe("シルトクレーテ");
result[0].MakerId.ShouldBe("RG40741");
result[0].Creators.ShouldBe(["柚木つばめ"]);
result[0].Genres.ShouldBe(["体験版"]);
result[0].Tags.ShouldBe(["バイノーラル/ダミヘ", "手コキ", "足コキ", "パイズリ", "言葉責め", "焦らし", "乳首責め", "本番なし"]);
result[0].Type.ShouldBe(DLSiteWorkType.Released);
result[0].Downloads.ShouldBe(1220);
}
[Fact] [Fact]
public async Task Scan_With_English_Locale() public async Task Scan_With_English_Locale()
{ {
@@ -57,4 +96,43 @@ public class VoiceWorkScannerTests
result[1].ProductId.ShouldBe("RJ00000002"); result[1].ProductId.ShouldBe("RJ00000002");
result[1].Type.ShouldBe(DLSiteWorkType.Announced); result[1].Type.ShouldBe(DLSiteWorkType.Announced);
} }
[Fact]
public async Task Scan_With_Updated_English_Locale()
{
string html = await ReadResourceAsync("English-Page-Updated.html");
IHttpService httpService = Substitute.For<IHttpService>();
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
.Returns(Task.FromResult(html));
HtmlLoader loader = new(httpService);
EnglishVoiceWorksScanner scanner = new(loader);
VoiceWorkScanOptions options = new(
PageNumber: 1,
PageSize: 100,
ExcludeAIGeneratedWorks: true,
ExcludePartiallyAIGeneratedWorks: true,
ExcludedMakerIds: []
);
var result = await scanner.ScanPageAsync(options, CancellationToken.None);
result.Count.ShouldBe(1);
result[0].ExpectedDate.ShouldBeNull();
result[0].SalesDate.ShouldBe(new DateOnly(2025, 10, 16));
result[0].ProductId.ShouldBe("RJ01455722");
result[0].ProductName.ShouldBe("[ENG Sub] Welcome to Soleil!");
result[0].Description.ShouldBe(string.Empty); // Waiting on this to get fixed on the site
result[0].Maker.ShouldBe("Translators Unite");
result[0].MakerId.ShouldBe("RG60289");
result[0].Creators.ShouldBe(["沼倉愛美"]);
result[0].Genres.ShouldBe(["All Ages", "Trial version"]);
result[0].Tags.ShouldBe(["Moe", "Healing", "Binaural", "ASMR", "Ear Cleaning", "Slice of Life / Daily Living", "Heartwarming", "Whispering"]);
result[0].Type.ShouldBe(DLSiteWorkType.Released);
result[0].Downloads.ShouldBe(1);
}
} }