Updated scanner logic to handle thumb VueJS components. Removed uneeded DLSiteWork fields.
This commit is contained in:
@@ -4,7 +4,7 @@ namespace JSMR.Application.Scanning.Contracts;
|
|||||||
|
|
||||||
public class DLSiteWork
|
public class DLSiteWork
|
||||||
{
|
{
|
||||||
public DLSiteWorkType Type { get; set; }
|
//public DLSiteWorkType Type { get; set; }
|
||||||
public DLSiteWorkCategory Category { get; set; }
|
public DLSiteWorkCategory Category { get; set; }
|
||||||
public required string ProductName { get; set; }
|
public required string ProductName { get; set; }
|
||||||
public required string ProductId { get; set; }
|
public required string ProductId { get; set; }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace JSMR.Application.Scanning.Contracts;
|
namespace JSMR.Application.Scanning.Contracts;
|
||||||
|
|
||||||
public enum DLSiteWorkType
|
//public enum DLSiteWorkType
|
||||||
{
|
//{
|
||||||
Released,
|
// Released,
|
||||||
Announced
|
// Announced
|
||||||
}
|
//}
|
||||||
@@ -2,37 +2,26 @@
|
|||||||
|
|
||||||
namespace JSMR.Infrastructure.Scanning.Models;
|
namespace JSMR.Infrastructure.Scanning.Models;
|
||||||
|
|
||||||
public class DLSiteHtmlDocument
|
public class DLSiteHtmlDocument(HtmlDocument document)
|
||||||
{
|
{
|
||||||
private readonly HtmlNodeCollection _workColumns;
|
private readonly HtmlNodeCollection _workColumns = document.DocumentNode.SelectNodes("//dl[@class='work_1col']");
|
||||||
private readonly HtmlNodeCollection _workColumnRights;
|
private readonly HtmlNodeCollection _workColumnRights = document.DocumentNode.SelectNodes("//td[contains(@class, 'work_1col_right')]");
|
||||||
private readonly HtmlNodeCollection _workThumbs;
|
private readonly HtmlNodeCollection _workThumbs = document.DocumentNode.SelectNodes("//div[@class='work_thumb']");
|
||||||
|
public HtmlNode PageTotalNode { get; } = document.DocumentNode.SelectNodes("//div[@class='page_total']/strong")[0];
|
||||||
|
|
||||||
public HtmlNode PageTotalNode { get; }
|
public DLSiteHtmlNode[] GetDLSiteNodes()
|
||||||
|
|
||||||
public DLSiteHtmlDocument(HtmlDocument document)
|
|
||||||
{
|
{
|
||||||
_workColumns = document.DocumentNode.SelectNodes("//dl[@class='work_1col']");
|
List<DLSiteHtmlNode> nodes = [];
|
||||||
//_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']");
|
|
||||||
|
|
||||||
PageTotalNode = document.DocumentNode.SelectNodes("//div[@class='page_total']/strong")[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DLSiteHtmlNode> GetDLSiteNodes()
|
|
||||||
{
|
|
||||||
var nodes = new List<DLSiteHtmlNode>();
|
|
||||||
|
|
||||||
if (_workColumns.Count != _workColumnRights.Count || _workColumns.Count != _workThumbs.Count)
|
if (_workColumns.Count != _workColumnRights.Count || _workColumns.Count != _workThumbs.Count)
|
||||||
throw new Exception("Work column node counts do not match!");
|
throw new Exception("Work column node counts do not match!");
|
||||||
|
|
||||||
for (int i = 0; i < _workColumns.Count; i++)
|
for (int i = 0; i < _workColumns.Count; i++)
|
||||||
{
|
{
|
||||||
var node = new DLSiteHtmlNode(_workColumns[i], _workColumnRights[i], _workThumbs[i]);
|
DLSiteHtmlNode node = new(_workColumns[i], _workColumnRights[i], _workThumbs[i]);
|
||||||
nodes.Add(node);
|
nodes.Add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes;
|
return [.. nodes];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,8 @@ public class DLSiteHtmlNode
|
|||||||
public HtmlNode? SalesDateNode { 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 HtmlNode? ThumbWithNgFilterBlockNode { get; private set; }
|
||||||
public HtmlNode[] GenreNodes { get; private set; }
|
public HtmlNode[] GenreNodes { get; private set; }
|
||||||
public HtmlNode[] SearchTagNodes { get; private set; }
|
public HtmlNode[] SearchTagNodes { get; private set; }
|
||||||
public HtmlNode[] CreatorNodes { get; private set; }
|
public HtmlNode[] CreatorNodes { get; private set; }
|
||||||
@@ -55,7 +56,8 @@ public class DLSiteHtmlNode
|
|||||||
|
|
||||||
//InitializeSalesAndDownloadsNodes();
|
//InitializeSalesAndDownloadsNodes();
|
||||||
StarRatingNode = GetStarRatingNode();
|
StarRatingNode = GetStarRatingNode();
|
||||||
ImageNode = GetImageNode();
|
ImageNode = TryGetImageNode();
|
||||||
|
ThumbWithNgFilterBlockNode = ThumbNode.SelectSingleNode(".//thumb-with-ng-filter-block");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HtmlNode[] GetGenreNodes()
|
private HtmlNode[] GetGenreNodes()
|
||||||
@@ -165,10 +167,13 @@ public class DLSiteHtmlNode
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
private HtmlNode GetImageNode()
|
private HtmlNode? TryGetImageNode()
|
||||||
{
|
{
|
||||||
HtmlNode linkNode = ThumbNode.SelectNodes(".//a")[0];
|
HtmlNode? linkNode = ThumbNode.SelectSingleNode(".//a");
|
||||||
|
|
||||||
return linkNode.SelectNodes(".//img")[0];
|
if (linkNode is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return linkNode.SelectSingleNode(".//img");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
|
||||||
namespace JSMR.Infrastructure.Scanning;
|
namespace JSMR.Infrastructure.Scanning;
|
||||||
@@ -45,4 +46,26 @@ public static class ScannerUtilities
|
|||||||
|
|
||||||
return imageSource;
|
return imageSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string[] ParseJavaScriptArray(string value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string json = NormalizeJavaScriptArray(value);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<string[]>(json) ?? [];
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return [.. value
|
||||||
|
.Trim('[', ']')
|
||||||
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(x => x.Trim().Trim('\'', '"'))];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeJavaScriptArray(string input)
|
||||||
|
{
|
||||||
|
return input.Trim().Replace('\'', '"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ using JSMR.Domain.ValueObjects;
|
|||||||
using JSMR.Infrastructure.Http;
|
using JSMR.Infrastructure.Http;
|
||||||
using JSMR.Infrastructure.Scanning.Models;
|
using JSMR.Infrastructure.Scanning.Models;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace JSMR.Infrastructure.Scanning;
|
namespace JSMR.Infrastructure.Scanning;
|
||||||
@@ -22,7 +23,7 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
public async Task<IReadOnlyList<DLSiteWork>> ScanPageAsync(VoiceWorkScanOptions options, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<DLSiteWork>> ScanPageAsync(VoiceWorkScanOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
DLSiteHtmlDocument document = await GetDLSiteHtmlCollectionAsync(options, cancellationToken);
|
DLSiteHtmlDocument document = await GetDLSiteHtmlCollectionAsync(options, cancellationToken);
|
||||||
List<DLSiteHtmlNode> nodes = document.GetDLSiteNodes();
|
DLSiteHtmlNode[] nodes = document.GetDLSiteNodes();
|
||||||
|
|
||||||
return GetDLSiteWorks(nodes, options);
|
return GetDLSiteWorks(nodes, options);
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
return filterBuilder.BuildSearchQuery(options.PageNumber, options.PageSize);
|
return filterBuilder.BuildSearchQuery(options.PageNumber, options.PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<DLSiteWork> GetDLSiteWorks(List<DLSiteHtmlNode> nodes, VoiceWorkScanOptions options)
|
private List<DLSiteWork> GetDLSiteWorks(DLSiteHtmlNode[] nodes, VoiceWorkScanOptions options)
|
||||||
{
|
{
|
||||||
var works = new List<DLSiteWork>();
|
var works = new List<DLSiteWork>();
|
||||||
|
|
||||||
@@ -73,8 +74,7 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
{
|
{
|
||||||
string productUrl = node.ProductLinkNode.Attributes["href"].Value;
|
string productUrl = node.ProductLinkNode.Attributes["href"].Value;
|
||||||
string makerUrl = node.MakerLinkNode.Attributes["href"].Value;
|
string makerUrl = node.MakerLinkNode.Attributes["href"].Value;
|
||||||
string imageSource = ScannerUtilities.GetImageSource(node.ImageNode);
|
(string imageSource, string imageUrl) = TryGetImageSourceAndUrl(node);
|
||||||
string imageUrl = imageSource.Replace("_sam.jpg", "_main.jpg").Replace("_sam.gif", "_main.gif");
|
|
||||||
ScannedRating? rating = GetScannedRating(node.StarRatingNode);
|
ScannedRating? rating = GetScannedRating(node.StarRatingNode);
|
||||||
|
|
||||||
DLSiteWork work = new()
|
DLSiteWork work = new()
|
||||||
@@ -89,7 +89,7 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
Creators = ScannerUtilities.GetStringListFromNodes(node.CreatorNodes),
|
Creators = ScannerUtilities.GetStringListFromNodes(node.CreatorNodes),
|
||||||
SmallImageUrl = imageSource,
|
SmallImageUrl = imageSource,
|
||||||
ImageUrl = imageUrl,
|
ImageUrl = imageUrl,
|
||||||
Type = imageUrl.Contains("ana/doujin") ? DLSiteWorkType.Announced : DLSiteWorkType.Released,
|
//Type = imageUrl.Contains("ana/doujin") ? DLSiteWorkType.Announced : DLSiteWorkType.Released,
|
||||||
StarRating = rating?.Score,
|
StarRating = rating?.Score,
|
||||||
Votes = rating?.Votes,
|
Votes = rating?.Votes,
|
||||||
AgeRating = GetAgeRating(node.GenreNodes)
|
AgeRating = GetAgeRating(node.GenreNodes)
|
||||||
@@ -113,6 +113,36 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (string, string) TryGetImageSourceAndUrl(DLSiteHtmlNode node)
|
||||||
|
{
|
||||||
|
if (node.ThumbWithNgFilterBlockNode is not null)
|
||||||
|
{
|
||||||
|
string candidates = node.ThumbWithNgFilterBlockNode.GetAttributeValue(":thumb-candidates", string.Empty);
|
||||||
|
string[] imageUrls = ScannerUtilities.ParseJavaScriptArray(candidates);
|
||||||
|
|
||||||
|
if (imageUrls.Length == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("No thumb candidartes found");
|
||||||
|
}
|
||||||
|
|
||||||
|
string imageSource = imageUrls[0];
|
||||||
|
string imageUrl = imageSource.Replace("_sam.jpg", "_main.jpg").Replace("_sam.gif", "_main.gif");
|
||||||
|
|
||||||
|
return (imageSource, imageUrl);
|
||||||
|
}
|
||||||
|
else if (node.ImageNode is not null)
|
||||||
|
{
|
||||||
|
string imageSource = ScannerUtilities.GetImageSource(node.ImageNode);
|
||||||
|
string imageUrl = imageSource.Replace("_sam.jpg", "_main.jpg").Replace("_sam.gif", "_main.gif");
|
||||||
|
|
||||||
|
return (imageSource, imageUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Unable to find image source and/or url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static AgeRating GetAgeRating(HtmlNode[] genreNodes)
|
private static AgeRating GetAgeRating(HtmlNode[] genreNodes)
|
||||||
{
|
{
|
||||||
List<string> genres = ScannerUtilities.GetStringListFromNodes(genreNodes);
|
List<string> genres = ScannerUtilities.GetStringListFromNodes(genreNodes);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<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\English-Page-Updated.html" />
|
||||||
|
<EmbeddedResource Include="Scanning\Japanese-Page-Updated.html" />
|
||||||
<EmbeddedResource Include="Scanning\Japanese-Page.html" />
|
<EmbeddedResource Include="Scanning\Japanese-Page.html" />
|
||||||
<EmbeddedResource Include="Scanning\English-Page.html" />
|
<EmbeddedResource Include="Scanning\English-Page.html" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
183
JSMR.Tests/Scanning/Japanese-Page-Updated.html
Normal file
183
JSMR.Tests/Scanning/Japanese-Page-Updated.html
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<html lang="ja-JP">
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="main">
|
||||||
|
<div id="main_inner">
|
||||||
|
<div>
|
||||||
|
<div data-toggle="found" class="sort_box border_b pb10">
|
||||||
|
<div class="page_total">
|
||||||
|
<strong>6670</strong>
|
||||||
|
<span>件中</span>
|
||||||
|
<strong>1~30</strong>
|
||||||
|
<span>件目</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="search_result_list" class="loading_display_open" data-toggle="found">
|
||||||
|
<table class="work_1col_table n_worklist" cellspacing="0">
|
||||||
|
<tbody>
|
||||||
|
<!-- RJ01536422 (With Image) -->
|
||||||
|
<tr data-list_item_product_id="RJ01536422" class=" ">
|
||||||
|
<td class="work_1col_thumb">
|
||||||
|
<div class="work_thumb">
|
||||||
|
<div class="work_thumb_inner" data-vue-component="thumb-img-popup">
|
||||||
|
<thumb-with-ng-filter-block ref="popup_img"
|
||||||
|
link="https://www.dlsite.com/maniax/announce/=/product_id/RJ01536422.html"
|
||||||
|
:thumb-candidates="['//img.dlsite.jp/resize/images2/ana/doujin/RJ01537000/RJ01536422_ana_img_main_240x240.webp','//img.dlsite.jp/resize/images2/ana/doujin/RJ01537000/RJ01536422_ana_img_main_240x240.jpg']"
|
||||||
|
alt="珈琲屋 綴 / いつもいつでも〜Alone with you〜 [喫茶綴]"
|
||||||
|
@mouseenter="showPopupImg">
|
||||||
|
</thumb-with-ng-filter-block>
|
||||||
|
<div v-cloak class="work_img_popover">
|
||||||
|
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7"
|
||||||
|
:src="is_show ? '//img.dlsite.jp/modpub/images2/ana/doujin/RJ01537000/RJ01536422_ana_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'"
|
||||||
|
alt="珈琲屋 綴 / いつもいつでも〜Alone with you〜 [喫茶綴]">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="work_category type_SOU">
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/work_type/SOU">ボイス・ASMR</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<dl class="work_1col">
|
||||||
|
<dt class="work_name">
|
||||||
|
<p class="expected_date">2026年12月下旬 発売予定</p>
|
||||||
|
<div class="icon_wrap"></div>
|
||||||
|
<a href="https://www.dlsite.com/maniax/announce/=/product_id/RJ01536422.html">
|
||||||
|
珈琲屋 綴 / いつもいつでも〜Alone with you〜
|
||||||
|
</a>
|
||||||
|
</dt>
|
||||||
|
<dd class="maker_name">
|
||||||
|
<a href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG36156.html">喫茶綴</a>
|
||||||
|
<span class="separator">/</span>
|
||||||
|
<span class="author">
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/keyword_creater/%22%E9%87%8E%E4%B8%8A%E8%8F%9C%E6%9C%88%22/ana_flg/all"
|
||||||
|
class="">野上菜月</a>
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
<dd class="work_text">
|
||||||
|
珈琲に特化した喫茶店、喫茶綴、外伝。『珈琲屋 綴』の従業員、綴明日菜が、大好きな珈琲と、貴方との時間を大切に育みます。珈琲に特化した喫茶店、喫茶綴の外伝です。CV:野上菜月様
|
||||||
|
</dd>
|
||||||
|
<dd class="work_genre">
|
||||||
|
<span class="icon_GEN" title="全年齢">全年齢</span><span data-vue-component="product-coupon" data-product_id="RJ01536422" v-cloak></span>
|
||||||
|
<input type="hidden" class="__product_attributes"
|
||||||
|
name="__product_attributes" id="_RJ01536422"
|
||||||
|
value="RG36156,male,SOU,JPN,SND,497,056,496,008,442,058"
|
||||||
|
disabled="disabled">
|
||||||
|
</dd>
|
||||||
|
<dd class="search_tag">
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/497/from/work.genre">ASMR</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/056/from/work.genre">癒し</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/058/from/work.genre">オールハッピー</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/496/from/work.genre">バイノーラル/ダミヘ</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/008/from/work.genre">日常/生活</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/442/from/work.genre">耳かき</a>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</td>
|
||||||
|
<td class="work_1col_right">
|
||||||
|
<ul>
|
||||||
|
<li class="sales_date">
|
||||||
|
予告開始日: 2026年01月01日
|
||||||
|
</li>
|
||||||
|
<li class="work_dl clear">
|
||||||
|
<div data-vue-component="product-wishlist-count"
|
||||||
|
data-product_id="RJ01536422" v-cloak></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div data-vue-component="product-item" data-product_id="RJ01536422"
|
||||||
|
data-layout="fsr_announce_only" data-is_ana="1" class="btn_2col"
|
||||||
|
data-usesample="true" data-samples="[]">
|
||||||
|
<p class="work_free_sample">
|
||||||
|
<a href="https://www.dlsite.com/maniax/announce/=/product_id/RJ01536422.html"
|
||||||
|
class="btn_free_sample disabled" data-product-id="RJ01536422">無料サンプル</a>
|
||||||
|
</p>
|
||||||
|
<p class="work_favorite_xs">
|
||||||
|
<a href="https://www.dlsite.com/maniax/mypage/wishlist/=/product_id/RJ01536422.html"
|
||||||
|
class="btn_favorite _btn_favorite" title="お気に入りに追加">お気に入りに追加</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- RJ01393816 (No Image) -->
|
||||||
|
<tr data-list_item_product_id="RJ01393816" class=" ">
|
||||||
|
<td class="work_1col_thumb">
|
||||||
|
<div class="work_thumb">
|
||||||
|
<div class="work_thumb_inner" data-vue-component="thumb-img-popup">
|
||||||
|
<thumb-with-ng-filter-block ref="popup_img" link="https://www.dlsite.com/maniax/announce/=/product_id/RJ01393816.html" :thumb-candidates="['//www.dlsite.com/images/web/home/no_img_main.gif','//www.dlsite.com/images/web/home/no_img_main.gif']" alt="アダルトグッズショップの店長にオナ禁でオモチャにされる話 [平たい胸族]" @mouseenter="showPopupImg"></thumb-with-ng-filter-block>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="work_category type_SOU"><a href="https://www.dlsite.com/maniax/fsr/=/work_type/SOU">ボイス・ASMR</a></div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
<dl class="work_1col">
|
||||||
|
<dt class="work_name">
|
||||||
|
|
||||||
|
<p class="expected_date">
|
||||||
|
2026年12月下旬 発売予定
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="icon_wrap">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<a href="https://www.dlsite.com/maniax/announce/=/product_id/RJ01393816.html">アダルトグッズショップの店長にオナ禁でオモチャにされる話</a>
|
||||||
|
</dt>
|
||||||
|
<dd class="maker_name">
|
||||||
|
<a href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG01044380.html">平たい胸族</a>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
|
||||||
|
<dd class="work_text">アダルトグッズショップでダウナーなセンパイと仕事中にオナ禁サポートをしてサボっていたことが店長にバレてしまった。今度はセンパイの詩乃と店長のミチル、2人にオナ禁でオモチャにされることになってしまった。</dd>
|
||||||
|
|
||||||
|
<dd class="work_genre">
|
||||||
|
<span data-vue-component="product-coupon" data-product_id="RJ01393816" v-cloak></span>
|
||||||
|
<input type="hidden" class="__product_attributes" name="__product_attributes" id="_RJ01393816" value="RG01044380,adl,male,SOU,JPN,SND,497,118,183,187,158,496,440,448" disabled="disabled">
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dd class="search_tag">
|
||||||
|
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/497/from/work.genre">ASMR</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/496/from/work.genre">バイノーラル/ダミヘ</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/448/from/work.genre">色仕掛け</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/440/from/work.genre">浮気</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/158/from/work.genre">百合</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/118/from/work.genre">レズ/女同士</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/187/from/work.genre">ツルペタ</a>
|
||||||
|
<a href="https://www.dlsite.com/maniax/fsr/=/genre/183/from/work.genre">貧乳/微乳</a>
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
</dl>
|
||||||
|
</td>
|
||||||
|
<td class="work_1col_right">
|
||||||
|
<ul>
|
||||||
|
<li class="sales_date">
|
||||||
|
予告開始日: 2025年05月15日
|
||||||
|
</li>
|
||||||
|
<li class="work_dl clear">
|
||||||
|
<div data-vue-component="product-wishlist-count" data-product_id="RJ01393816" v-cloak></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div data-vue-component="product-item" data-product_id="RJ01393816" data-layout="fsr_announce_only" data-is_ana="1" class="btn_2col" data-usesample="true" data-samples="[]">
|
||||||
|
<p class="work_free_sample"><a href="https://www.dlsite.com/maniax/announce/=/product_id/RJ01393816.html" class="btn_free_sample disabled" data-product-id="RJ01393816">無料サンプル</a></p>
|
||||||
|
<p class="work_favorite_xs"><a href="https://www.dlsite.com/maniax/mypage/wishlist/=/product_id/RJ01393816.html" class="btn_favorite _btn_favorite" title="お気に入りに追加">お気に入りに追加</a></p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -49,10 +49,58 @@ public class VoiceWorkScannerTests
|
|||||||
result[0].Creators.ShouldBe(["柚木つばめ"]);
|
result[0].Creators.ShouldBe(["柚木つばめ"]);
|
||||||
result[0].Genres.ShouldBe(["体験版"]);
|
result[0].Genres.ShouldBe(["体験版"]);
|
||||||
result[0].Tags.ShouldBe(["バイノーラル/ダミヘ", "手コキ", "足コキ", "パイズリ", "言葉責め", "焦らし", "乳首責め", "本番なし"]);
|
result[0].Tags.ShouldBe(["バイノーラル/ダミヘ", "手コキ", "足コキ", "パイズリ", "言葉責め", "焦らし", "乳首責め", "本番なし"]);
|
||||||
result[0].Type.ShouldBe(DLSiteWorkType.Released);
|
|
||||||
result[0].Downloads.ShouldBe(1220);
|
result[0].Downloads.ShouldBe(1220);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Scan_With_Updated_Japanese_Locale()
|
||||||
|
{
|
||||||
|
string html = await ReadResourceAsync("Japanese-Page-Updated.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(2);
|
||||||
|
|
||||||
|
result[0].SalesDate.ShouldBeNull();
|
||||||
|
result[0].ExpectedDate.ShouldBe(new DateOnly(2026, 12, 21));
|
||||||
|
result[0].ProductId.ShouldBe("RJ01536422");
|
||||||
|
result[0].ProductName.ShouldBe("珈琲屋 綴 / いつもいつでも〜Alone with you〜");
|
||||||
|
result[0].Description.ShouldBe("珈琲に特化した喫茶店、喫茶綴、外伝。『珈琲屋 綴』の従業員、綴明日菜が、大好きな珈琲と、貴方との時間を大切に育みます。珈琲に特化した喫茶店、喫茶綴の外伝です。CV:野上菜月様");
|
||||||
|
result[0].Maker.ShouldBe("喫茶綴");
|
||||||
|
result[0].MakerId.ShouldBe("RG36156");
|
||||||
|
result[0].Creators.ShouldBe(["野上菜月"]);
|
||||||
|
result[0].Genres.ShouldBe(["全年齢"]);
|
||||||
|
result[0].Tags.ShouldBe(["ASMR", "癒し", "オールハッピー", "バイノーラル/ダミヘ", "日常/生活", "耳かき"]);
|
||||||
|
// TODO: Wishlist count?
|
||||||
|
|
||||||
|
result[1].SalesDate.ShouldBeNull();
|
||||||
|
result[1].ExpectedDate.ShouldBe(new DateOnly(2026, 12, 21));
|
||||||
|
result[1].ProductId.ShouldBe("RJ01393816");
|
||||||
|
result[1].ProductName.ShouldBe("アダルトグッズショップの店長にオナ禁でオモチャにされる話");
|
||||||
|
result[1].Description.ShouldBe("アダルトグッズショップでダウナーなセンパイと仕事中にオナ禁サポートをしてサボっていたことが店長にバレてしまった。今度はセンパイの詩乃と店長のミチル、2人にオナ禁でオモチャにされることになってしまった。");
|
||||||
|
result[1].Maker.ShouldBe("平たい胸族");
|
||||||
|
result[1].MakerId.ShouldBe("RG01044380");
|
||||||
|
result[1].Creators.ShouldBe([]);
|
||||||
|
result[1].Genres.ShouldBe([]);
|
||||||
|
result[1].Tags.ShouldBe(["ASMR", "バイノーラル/ダミヘ", "色仕掛け", "浮気", "百合", "レズ/女同士", "ツルペタ", "貧乳/微乳"]);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Scan_With_English_Locale()
|
public async Task Scan_With_English_Locale()
|
||||||
{
|
{
|
||||||
@@ -88,13 +136,11 @@ public class VoiceWorkScannerTests
|
|||||||
result[0].Creators.ShouldBe(["Some Creator"]);
|
result[0].Creators.ShouldBe(["Some Creator"]);
|
||||||
result[0].Genres.ShouldBe(["Voice", "Trial version"]);
|
result[0].Genres.ShouldBe(["Voice", "Trial version"]);
|
||||||
result[0].Tags.ShouldBe(["Male Protagonist", "Gal", "Uniform", "Harem", "Big Breasts", "Tanned Skin / Suntan"]);
|
result[0].Tags.ShouldBe(["Male Protagonist", "Gal", "Uniform", "Harem", "Big Breasts", "Tanned Skin / Suntan"]);
|
||||||
result[0].Type.ShouldBe(DLSiteWorkType.Released);
|
|
||||||
result[0].Downloads.ShouldBe(1000);
|
result[0].Downloads.ShouldBe(1000);
|
||||||
|
|
||||||
result[1].ExpectedDate.ShouldBe(new DateOnly(2025, 10, 11));
|
result[1].ExpectedDate.ShouldBe(new DateOnly(2025, 10, 11));
|
||||||
result[1].SalesDate.ShouldBeNull();
|
result[1].SalesDate.ShouldBeNull();
|
||||||
result[1].ProductId.ShouldBe("RJ00000002");
|
result[1].ProductId.ShouldBe("RJ00000002");
|
||||||
result[1].Type.ShouldBe(DLSiteWorkType.Announced);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -132,7 +178,6 @@ public class VoiceWorkScannerTests
|
|||||||
result[0].Creators.ShouldBe(["沼倉愛美"]);
|
result[0].Creators.ShouldBe(["沼倉愛美"]);
|
||||||
result[0].Genres.ShouldBe(["All Ages", "Trial version"]);
|
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].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);
|
result[0].Downloads.ShouldBe(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user