Skip to content

Commit 9a86f29

Browse files
committed
Fixed missing levels for the Zelda Hanayama Huzzle puzzles
1 parent d55f87b commit 9a86f29

File tree

4 files changed

+60
-23
lines changed

4 files changed

+60
-23
lines changed

src/recipes/hanayama_huzzles/HanayamaHuzzlesRecipe.ts

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,44 @@ import { RecipeMarker } from '../helpers/RecipeMarker';
1010
import { HanayamaHuzzle } from './HanayamaHuzzle';
1111
import { HanayamaHuzzlesRecipeSettings } from './settings/HanayamaHuzzlesRecipeSettings';
1212

13+
interface HanayamaHuzzlesScrapeDataSource {
14+
url: string;
15+
level?: string;
16+
}
17+
1318
export class HanayamaHuzzlesRecipe implements Recipe {
1419
static readonly NAME = 'Hanayama Huzzles';
1520
static readonly WEBPAGE = 'https://hanayama-toys.com/product-category/puzzles/huzzle';
16-
static readonly CHESS_PUZZLES_WEBPAGE = 'https://hanayama-toys.com/product-category/puzzles/huzzle/chess-puzzle';
21+
static readonly CHESS_PUZZLES_DATA_SOURCE: HanayamaHuzzlesScrapeDataSource = {
22+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/chess-puzzle'
23+
};
1724

1825
static readonly #HEADERS: readonly string[] = ['Level', 'Index', 'Name', 'Picture', 'Status'];
19-
static readonly #SCRAPE_URLS: readonly string[] = [
20-
'https://hanayama-toys.com/product-category/puzzles/huzzle/level-1-fun',
21-
'https://hanayama-toys.com/product-category/puzzles/huzzle/level-2-easy',
22-
'https://hanayama-toys.com/product-category/puzzles/huzzle/level-3-normal',
23-
'https://hanayama-toys.com/product-category/puzzles/huzzle/level-4-hard',
24-
'https://hanayama-toys.com/product-category/puzzles/huzzle/level-5-expert',
25-
'https://hanayama-toys.com/product-category/puzzles/huzzle/level-6-grand-master'
26+
static readonly #SCRAPE_DATA_SOURCES: readonly HanayamaHuzzlesScrapeDataSource[] = [
27+
{
28+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/level-1-fun',
29+
level: '1',
30+
},
31+
{
32+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/level-2-easy',
33+
level: '2',
34+
},
35+
{
36+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/level-3-normal',
37+
level: '3',
38+
},
39+
{
40+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/level-4-hard',
41+
level: '4',
42+
},
43+
{
44+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/level-5-expert',
45+
level: '5',
46+
},
47+
{
48+
url: 'https://hanayama-toys.com/product-category/puzzles/huzzle/level-6-grand-master',
49+
level: '6',
50+
}
2651
];
2752

2853
#marker = new RecipeMarker(HanayamaHuzzlesRecipe.NAME);
@@ -52,24 +77,29 @@ export class HanayamaHuzzlesRecipe implements Recipe {
5277
}
5378

5479
async #scrapeHuzzles(): Promise<HanayamaHuzzle[]> {
55-
const metadataRegex = new RegExp(/\w+[ ](?<level>\d+)-(?<index>\d+)[ ](?<name>.+)/); // https://regex101.com/r/1vGzHd/2
80+
const metadataRegex = new RegExp(/\w+[ ]\d+-(?<index>\d+)[ ](?<name>.+)/); // https://regex101.com/r/1vGzHd/3
5681

57-
const urls = [...HanayamaHuzzlesRecipe.#SCRAPE_URLS];
82+
const dataSources = [...HanayamaHuzzlesRecipe.#SCRAPE_DATA_SOURCES];
5883
if (this.settings.includeChessPuzzles) {
59-
urls.push(HanayamaHuzzlesRecipe.CHESS_PUZZLES_WEBPAGE);
84+
dataSources.push(HanayamaHuzzlesRecipe.CHESS_PUZZLES_DATA_SOURCE);
6085
}
61-
const scraper = new WebsiteScraper(urls);
86+
const scraper = new WebsiteScraper(
87+
dataSources.map(dataSource => ({
88+
url: dataSource.url,
89+
context: dataSource.level
90+
}))
91+
);
6292

6393
return await scraper.scrape(
6494
content => {
6595
return Array.from(content.querySelectorAll('#main > .products > .product'));
6696
},
67-
product => {
97+
(product, sourceLevel) => {
6898
const title = product.querySelector('.product-info > .product-title > a')?.textContent || '';
6999
const titleMatch = title.match(metadataRegex);
70100
const titleGroups = titleMatch?.groups;
71101

72-
const level = titleGroups != null ? titleGroups.level : 'N/A';
102+
const level = sourceLevel ?? 'N/A';
73103
const index = titleGroups != null ? titleGroups.index : 'N/A';
74104
const name = titleGroups != null ? titleGroups.name : title;
75105

src/recipes/hanayama_huzzles/settings/HanayamaHuzzlesRecipeExtraSettingsAdder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class HanayamaHuzzlesRecipeExtraSettingsAdder implements RecipeExtraSetti
1818
'Include chess puzzles',
1919
this.root.createFragment(
2020
this.root.createText('span', 'Whether to include the chess puzzles ('),
21-
this.root.createLink(HanayamaHuzzlesRecipe.CHESS_PUZZLES_WEBPAGE),
21+
this.root.createLink(HanayamaHuzzlesRecipe.CHESS_PUZZLES_DATA_SOURCE.url),
2222
this.root.createText('span', ') or not.')
2323
)
2424
);

src/recipes/iq_puzzles/IQPuzzlesRecipe.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ export class IQPuzzlesRecipe implements Recipe {
4444
async #scrapePuzzles(): Promise<IQPuzzle[]> {
4545
const nameRegex = new RegExp(/\s*(?<name>[\w\s]+)$/); // https://regex101.com/r/AuK9pb/2
4646
const cleanedLinkRegex = new RegExp(/^(?<cleanedLink>.+?\.jpg)/); // https://regex101.com/r/fd3A6U/1
47-
const scraper = new WebsiteScraper([IQPuzzlesRecipe.#SCRAPE_URL]);
47+
const scraper = new WebsiteScraper([{
48+
url: IQPuzzlesRecipe.#SCRAPE_URL
49+
}]);
4850

4951
return await scraper.scrape(
5052
content => {

src/scraping/WebsiteScraper.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import { WebpageDownloader } from './WebpageDownloader';
22

3-
export class WebsiteScraper {
4-
constructor(private urls: string[]) {}
3+
interface WebsiteScraperSource<Context> {
4+
url: string;
5+
context?: Context;
6+
}
7+
8+
export class WebsiteScraper<Context> {
9+
constructor(private sources: WebsiteScraperSource<Context>[]) {}
510

611
async scrape<T>(
712
parseWebpage: (webpage: Document) => Element[],
8-
parseElement: (elements: Element) => T
13+
parseElement: (elements: Element, context?: Context) => T
914
): Promise<T[]> {
1015
const elements = await Promise.all(
11-
this.urls
12-
.map(async url => {
13-
const downloader = new WebpageDownloader(url);
16+
this.sources
17+
.map(async source => {
18+
const downloader = new WebpageDownloader(source.url);
1419
const webpage = await downloader.download();
1520
const elements = parseWebpage(webpage);
1621

1722
return elements.map(element => {
18-
return parseElement(element);
23+
return parseElement(element, source.context);
1924
});
2025
})
2126
);

0 commit comments

Comments
 (0)