summaryrefslogblamecommitdiffstatshomepage
path: root/frontend/src/lib/Scraper.ts
blob: 4baf370222de57d505cbe256fe1522b38c835195 (plain) (tree)



























































































































































                                                                                                        
import {
	Category,
	Censorship,
	Direction,
	Language,
	Layout,
	OnMissing,
	Rating,
	type FullComicFragment,
	type ScrapedComic,
	type UpsertComicInput,
	type UpsertOptions
} from '$gql/graphql';
import {
	CategoryLabel,
	CensorshipLabel,
	DirectionLabel,
	LanguageLabel,
	LayoutLabel,
	RatingLabel
} from '$lib/Enums';
import { getContext, setContext } from 'svelte';
import { writable, type Writable } from 'svelte/store';

interface ScraperContext {
	scraper: string;
	warnings: string[];
	selector?: ScrapedComicSelector;
}

export function initScraperContext() {
	return setContext<Writable<ScraperContext>>('scraper', writable({ scraper: '', warnings: [] }));
}

export function getScraperContext() {
	return getContext<Writable<ScraperContext>>('scraper');
}

export class Selector<T extends string> {
	keep = true;
	value: T;
	display: string | undefined;

	constructor(value: T, display?: string) {
		this.value = value;
		this.display = display;
	}

	toString() {
		return this.display ?? this.value;
	}

	static from<T extends string>(
		scraped: T | undefined | null,
		have: string | undefined | null,
		label?: Record<string, string>
	) {
		if (scraped && have !== scraped) {
			return new Selector(scraped, label ? label[scraped] : undefined);
		}
		return undefined;
	}

	static fromList(scraped: string[], have: { name: string }[]) {
		const haves = new Set(have.map((i) => i.name));

		return scraped.filter((i) => !haves.has(i)).map((i) => new Selector(i));
	}
}

function keepItem<T extends string>(selector?: Selector<T>): T | undefined | null {
	if (selector?.keep) {
		return selector.value;
	}
	return undefined;
}

function keepList<T extends string>(
	selectorList: Selector<T>[],
	onMissing: OnMissing
): { names: T[]; options: UpsertOptions } {
	return {
		names: selectorList.filter((v) => v.keep).map((v) => v.value),
		options: { onMissing }
	};
}

export class ScrapedComicSelector {
	title?: Selector<string>;
	originalTitle?: Selector<string>;
	url?: Selector<string>;
	date?: Selector<string>;
	category?: Selector<Category>;
	censorship?: Selector<Censorship>;
	rating?: Selector<Rating>;
	language?: Selector<Language>;
	direction?: Selector<Direction>;
	layout?: Selector<Layout>;
	artists: Selector<string>[];
	circles: Selector<string>[];
	characters: Selector<string>[];
	worlds: Selector<string>[];
	tags: Selector<string>[];

	constructor(scraped: ScrapedComic, comic: FullComicFragment) {
		this.title = Selector.from(scraped.title, comic.title);
		this.originalTitle = Selector.from(scraped.originalTitle, comic.originalTitle);
		this.url = Selector.from(scraped.url, comic.url);
		this.date = Selector.from(scraped.date, comic.date);
		this.category = Selector.from(scraped.category, comic.category, CategoryLabel);
		this.censorship = Selector.from(scraped.censorship, comic.censorship, CensorshipLabel);
		this.rating = Selector.from(scraped.rating, comic.rating, RatingLabel);
		this.language = Selector.from(scraped.language, comic.language, LanguageLabel);
		this.direction = Selector.from(scraped.direction, comic.direction, DirectionLabel);
		this.layout = Selector.from(scraped.layout, comic.layout, LayoutLabel);

		this.artists = Selector.fromList(scraped.artists, comic.artists);
		this.circles = Selector.fromList(scraped.circles, comic.circles);
		this.characters = Selector.fromList(scraped.characters, comic.characters);
		this.tags = Selector.fromList(scraped.tags, comic.tags);
		this.worlds = Selector.fromList(scraped.worlds, comic.worlds);
	}

	hasData() {
		return (
			Object.values(this).filter((i) => {
				if (i === undefined) {
					return false;
				} else if (Array.isArray(i) && i.length === 0) {
					return false;
				}
				return true;
			}).length > 0
		);
	}

	toInput(onMissing: OnMissing): UpsertComicInput {
		return {
			title: keepItem(this.title),
			originalTitle: keepItem(this.originalTitle),
			url: keepItem(this.url),
			date: keepItem(this.date),
			category: keepItem(this.category),
			censorship: keepItem(this.censorship),
			rating: keepItem(this.rating),
			language: keepItem(this.language),
			direction: keepItem(this.direction),
			layout: keepItem(this.layout),
			artists: keepList(this.artists, onMissing),
			circles: keepList(this.circles, onMissing),
			characters: keepList(this.characters, onMissing),
			worlds: keepList(this.worlds, onMissing),
			tags: keepList(this.tags, onMissing)
		};
	}
}