Skip to content

Commit 5b9b7f3

Browse files
committed
Added IQ Puzzles recipe logic
1 parent 5bbfca4 commit 5b9b7f3

File tree

4 files changed

+136
-2
lines changed

4 files changed

+136
-2
lines changed

src/main.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ export default class TrackALotPlugin extends Plugin {
2929
}
3030

3131
if (settingsManager.settings.iqPuzzles) {
32-
this.#addCommand(IQPuzzlesRecipe.NAME, new IQPuzzlesRecipe());
32+
this.#addCommand(IQPuzzlesRecipe.NAME, new IQPuzzlesRecipe(
33+
markdownTableFactory,
34+
markdownTableConverter,
35+
trackablesUpdater
36+
));
3337
}
3438
}
3539

src/markdown/MarkdownTableFactory.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export class MarkdownTableFactory {
2424
return this.tableCellNode([this.textNode(text)]);
2525
}
2626

27+
imageTableCellNode(url: string, size: number): TableCell {
28+
return this.tableCellNode([this.imageNode(url, size)]);
29+
}
30+
2731
tableCellNode(contents: PhrasingContent[]): TableCell {
2832
return {
2933
type: 'tableCell',

src/recipes/iq_puzzles/IQPuzzle.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Trackable } from 'src/tracking/Trackable';
2+
3+
export class IQPuzzle implements Trackable {
4+
identifier: string;
5+
6+
constructor(
7+
public readonly name: string,
8+
public readonly imageLink: string,
9+
public readonly status = ''
10+
) {
11+
this.identifier = name;
12+
}
13+
14+
withStatus(newStatus: string): Trackable {
15+
return new IQPuzzle(this.name, this.imageLink, newStatus);
16+
}
17+
}
Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,119 @@
1+
import { MarkdownTableConverter } from 'src/markdown/MarkdownTableConverter';
2+
import { MarkdownTableFactory } from 'src/markdown/MarkdownTableFactory';
3+
import { RegexFactory } from 'src/regex/RegexFactory';
4+
import { WebsiteScraper } from 'src/scraping/WebsiteScraper';
5+
import { TrackablesUpdater } from 'src/tracking/TrackablesUpdater';
16
import { Recipe } from '../Recipe';
7+
import { RecipeListUpdater } from '../RecipeListUpdater';
8+
import { RecipeMarkdownListUpdater } from '../RecipeMarkdownListUpdater';
9+
import { RecipeMarker } from '../RecipeMarker';
10+
import { IQPuzzle } from './IQPuzzle';
211

312
export class IQPuzzlesRecipe implements Recipe {
413
static NAME = 'IQ Puzzles';
514
static WEBPAGE = 'https://www.iqpuzzle.com';
615

16+
static #HEADERS = ['Name', 'Picture', 'Status'];
17+
static #SCRAPE_URL = 'https://www.iqpuzzle.com';
18+
19+
#marker = new RecipeMarker(IQPuzzlesRecipe.NAME);
20+
21+
constructor(
22+
private markdownTableFactory: MarkdownTableFactory,
23+
private markdownTableConverter: MarkdownTableConverter,
24+
private trackablesUpdater: TrackablesUpdater
25+
) {}
26+
727
async updatedListInContent(content: string): Promise<string> {
8-
return content;
28+
const markdownUpdater = new RecipeMarkdownListUpdater(this.#marker);
29+
const updater = new RecipeListUpdater<IQPuzzle>(
30+
IQPuzzlesRecipe.#HEADERS,
31+
markdownUpdater,
32+
this.trackablesUpdater
33+
);
34+
35+
return await updater.update(
36+
content,
37+
38+
this.#markdownTableStringToPuzzles.bind(this),
39+
this.#scrapePuzzles.bind(this),
40+
this.#puzzlesToMarkdownTableString.bind(this)
41+
);
42+
}
43+
44+
async #scrapePuzzles(): Promise<IQPuzzle[]> {
45+
const nameRegex = new RegExp(/(?<name>\w+)$/); // https://regex101.com/r/AuK9pb/1
46+
const cleanedLinkRegex = new RegExp(/^(?<cleanedLink>.+?\.jpg)/); // https://regex101.com/r/fd3A6U/1
47+
const scraper = new WebsiteScraper([IQPuzzlesRecipe.#SCRAPE_URL]);
48+
49+
return await scraper.scrape(
50+
content => {
51+
const lists = Array.from(content.querySelectorAll('ul[data-hook="product-list-wrapper"]'));
52+
53+
if (lists.length < 2) {
54+
return [];
55+
}
56+
57+
const list = lists[1];
58+
59+
return Array.from(list.querySelectorAll('li'));
60+
},
61+
product => {
62+
const title = product.querySelector('div[data-hook="not-image-container"] a h3')?.textContent || '';
63+
const titleMatch = title.match(nameRegex);
64+
const titleGroups = titleMatch?.groups;
65+
66+
const name = titleGroups != null ? titleGroups.name : title;
67+
68+
const image = product.querySelector('a wow-image img');
69+
const imageLink = image != null ? (image as HTMLImageElement).src : '';
70+
const cleanedImageLinkMatch = imageLink.match(cleanedLinkRegex);
71+
const cleanedImageLink = (cleanedImageLinkMatch != null && cleanedImageLinkMatch.groups != null)
72+
? cleanedImageLinkMatch.groups.cleanedLink
73+
: '';
74+
75+
return new IQPuzzle(name, cleanedImageLink);
76+
}
77+
);
78+
}
79+
80+
#puzzlesToMarkdownTableString(headers: string[], puzzles: IQPuzzle[]): string {
81+
const headerRow = this.markdownTableFactory.tableRowNode(
82+
headers.map(header => this.markdownTableFactory.textTableCellNode(header))
83+
);
84+
const puzzleRows = puzzles.map(puzzle =>
85+
this.markdownTableFactory.tableRowNode([
86+
this.markdownTableFactory.textTableCellNode(puzzle.name),
87+
this.markdownTableFactory.imageTableCellNode(puzzle.imageLink, 100),
88+
this.markdownTableFactory.textTableCellNode(puzzle.status)
89+
])
90+
);
91+
const table = this.markdownTableFactory.table(headerRow, puzzleRows);
92+
93+
return this.markdownTableConverter.tableToString(table);
94+
}
95+
96+
#markdownTableStringToPuzzles(markdownTableString: string): IQPuzzle[] {
97+
const arrayOfArrays = this.markdownTableConverter.arrayOfArraysFromString(markdownTableString);
98+
const imageLinkRegex = new RegexFactory().imageMarkdownLinkRegex();
99+
100+
return arrayOfArrays.flatMap(array => {
101+
if (array.length < 5) {
102+
return [];
103+
}
104+
105+
const name = array[0];
106+
107+
const image = array[1];
108+
const imageLinkMatch = image.match(imageLinkRegex);
109+
if (imageLinkMatch == null || imageLinkMatch.groups == null) {
110+
return [];
111+
}
112+
const imageLink = imageLinkMatch.groups.link;
113+
114+
const status = array[2];
115+
116+
return new IQPuzzle(name, imageLink, status);
117+
});
9118
}
10119
}

0 commit comments

Comments
 (0)