Skip to content

Commit

Permalink
Added IQ Puzzles recipe logic
Browse files Browse the repository at this point in the history
  • Loading branch information
revolter committed Feb 24, 2024
1 parent 5bbfca4 commit 5b9b7f3
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 2 deletions.
6 changes: 5 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ export default class TrackALotPlugin extends Plugin {
}

if (settingsManager.settings.iqPuzzles) {
this.#addCommand(IQPuzzlesRecipe.NAME, new IQPuzzlesRecipe());
this.#addCommand(IQPuzzlesRecipe.NAME, new IQPuzzlesRecipe(
markdownTableFactory,
markdownTableConverter,
trackablesUpdater
));
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/markdown/MarkdownTableFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export class MarkdownTableFactory {
return this.tableCellNode([this.textNode(text)]);
}

imageTableCellNode(url: string, size: number): TableCell {
return this.tableCellNode([this.imageNode(url, size)]);
}

tableCellNode(contents: PhrasingContent[]): TableCell {
return {
type: 'tableCell',
Expand Down
17 changes: 17 additions & 0 deletions src/recipes/iq_puzzles/IQPuzzle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Trackable } from 'src/tracking/Trackable';

export class IQPuzzle implements Trackable {
identifier: string;

constructor(
public readonly name: string,
public readonly imageLink: string,
public readonly status = ''
) {
this.identifier = name;
}

withStatus(newStatus: string): Trackable {
return new IQPuzzle(this.name, this.imageLink, newStatus);
}
}
111 changes: 110 additions & 1 deletion src/recipes/iq_puzzles/IQPuzzlesRecipe.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,119 @@
import { MarkdownTableConverter } from 'src/markdown/MarkdownTableConverter';
import { MarkdownTableFactory } from 'src/markdown/MarkdownTableFactory';
import { RegexFactory } from 'src/regex/RegexFactory';
import { WebsiteScraper } from 'src/scraping/WebsiteScraper';
import { TrackablesUpdater } from 'src/tracking/TrackablesUpdater';
import { Recipe } from '../Recipe';
import { RecipeListUpdater } from '../RecipeListUpdater';
import { RecipeMarkdownListUpdater } from '../RecipeMarkdownListUpdater';
import { RecipeMarker } from '../RecipeMarker';
import { IQPuzzle } from './IQPuzzle';

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

static #HEADERS = ['Name', 'Picture', 'Status'];
static #SCRAPE_URL = 'https://www.iqpuzzle.com';

#marker = new RecipeMarker(IQPuzzlesRecipe.NAME);

constructor(
private markdownTableFactory: MarkdownTableFactory,
private markdownTableConverter: MarkdownTableConverter,
private trackablesUpdater: TrackablesUpdater
) {}

async updatedListInContent(content: string): Promise<string> {
return content;
const markdownUpdater = new RecipeMarkdownListUpdater(this.#marker);
const updater = new RecipeListUpdater<IQPuzzle>(
IQPuzzlesRecipe.#HEADERS,
markdownUpdater,
this.trackablesUpdater
);

return await updater.update(
content,

this.#markdownTableStringToPuzzles.bind(this),
this.#scrapePuzzles.bind(this),
this.#puzzlesToMarkdownTableString.bind(this)
);
}

async #scrapePuzzles(): Promise<IQPuzzle[]> {
const nameRegex = new RegExp(/(?<name>\w+)$/); // https://regex101.com/r/AuK9pb/1
const cleanedLinkRegex = new RegExp(/^(?<cleanedLink>.+?\.jpg)/); // https://regex101.com/r/fd3A6U/1
const scraper = new WebsiteScraper([IQPuzzlesRecipe.#SCRAPE_URL]);

return await scraper.scrape(
content => {
const lists = Array.from(content.querySelectorAll('ul[data-hook="product-list-wrapper"]'));

if (lists.length < 2) {
return [];
}

const list = lists[1];

return Array.from(list.querySelectorAll('li'));
},
product => {
const title = product.querySelector('div[data-hook="not-image-container"] a h3')?.textContent || '';
const titleMatch = title.match(nameRegex);
const titleGroups = titleMatch?.groups;

const name = titleGroups != null ? titleGroups.name : title;

const image = product.querySelector('a wow-image img');
const imageLink = image != null ? (image as HTMLImageElement).src : '';
const cleanedImageLinkMatch = imageLink.match(cleanedLinkRegex);
const cleanedImageLink = (cleanedImageLinkMatch != null && cleanedImageLinkMatch.groups != null)
? cleanedImageLinkMatch.groups.cleanedLink
: '';

return new IQPuzzle(name, cleanedImageLink);
}
);
}

#puzzlesToMarkdownTableString(headers: string[], puzzles: IQPuzzle[]): string {
const headerRow = this.markdownTableFactory.tableRowNode(
headers.map(header => this.markdownTableFactory.textTableCellNode(header))
);
const puzzleRows = puzzles.map(puzzle =>
this.markdownTableFactory.tableRowNode([
this.markdownTableFactory.textTableCellNode(puzzle.name),
this.markdownTableFactory.imageTableCellNode(puzzle.imageLink, 100),
this.markdownTableFactory.textTableCellNode(puzzle.status)
])
);
const table = this.markdownTableFactory.table(headerRow, puzzleRows);

return this.markdownTableConverter.tableToString(table);
}

#markdownTableStringToPuzzles(markdownTableString: string): IQPuzzle[] {
const arrayOfArrays = this.markdownTableConverter.arrayOfArraysFromString(markdownTableString);
const imageLinkRegex = new RegexFactory().imageMarkdownLinkRegex();

return arrayOfArrays.flatMap(array => {
if (array.length < 5) {
return [];
}

const name = array[0];

const image = array[1];
const imageLinkMatch = image.match(imageLinkRegex);
if (imageLinkMatch == null || imageLinkMatch.groups == null) {
return [];
}
const imageLink = imageLinkMatch.groups.link;

const status = array[2];

return new IQPuzzle(name, imageLink, status);
});
}
}

0 comments on commit 5b9b7f3

Please sign in to comment.