const rawTldList = () => import("@/data/tld.txt?raw"); const englishWords = () => import("@/data/english.txt?raw"); const frenchWords = () => import("@/data/french.txt?raw"); import { randomElement, shuffled } from "./random"; import type { Domain } from "@/types"; export class DomainFactory { error: string | null = null; words: Record = {}; generated: string[] = []; history: Domain[] = []; tldList: string[] = []; ignoredTldList: string[] = []; async init(lang: string | null = null) { if (lang && !this.words[lang]) { switch (lang) { case "en": this.words.en = this.initWords((await englishWords()).default); break; case "fr": this.words.fr = this.initWords((await frenchWords()).default); break; } } if (this.tldList.length == 0) { this.tldList = (await rawTldList()).default.trim().split("\n"); } } escapeWord(word: string): string { return word .normalize("NFD") .toLowerCase() .replace(/[^a-z0-9-]/g, "") .trim(); } initWords(raw: string): string[] { return raw .split("\n") .map((word) => { word = this.escapeWord(word); if (word.length == 0) { return null; } return word; }) .filter((word) => word !== null); } randomWord(lang: string): string { if (!this.words[lang]) { this.error = `No words for language: ${lang}`; throw new Error(this.error); } return randomElement(this.words[lang]); } randomDomain(word: string): string | null { for (const tld of shuffled(this.tldList.slice())) { if (this.ignoredTld(tld)) { continue; } const tldEscaped: string = tld.replace(".", ""); if (word.endsWith(tldEscaped) && word.length > tldEscaped.length + 2) { const domain = `${word.substring(0, word.length - tldEscaped.length)}.${tld}`; if (!domain.includes("-.") && !this.generated.includes(domain)) { this.generated.push(domain); return domain; } } } return null; } next(lang: string): Promise { return new Promise((resolve, reject) => { this.init(lang) .then(() => { let k = 10000; while (k > 0) { k--; const word = this.randomWord(lang); const domain = this.randomDomain(word); if (domain) { const output = this.newDomain(domain); this.history.push(output); resolve(output); return; } } this.error = "Could not generate a valid domain"; reject(new Error(this.error)); }) .catch(reject); }); } ignoreTlds(...tlds: string[]): void { this.ignoredTldList.push(...tlds); } newDomain(domain: string): Domain { return { name: domain, tld: domain.split(".").slice(1).join("."), searching: false, available: true, }; } ignoredTld(tld: string): boolean { return this.ignoredTldList.some((ignoredTld: string) => tld.endsWith(ignoredTld)); } allFromWord(word: string): Promise { const escapedWord = this.escapeWord(word.replace(/ /gm, "-")); return new Promise((resolve, reject) => { this.init() .then(() => { const domains = []; for (const tld of shuffled(this.tldList.slice())) { if (this.ignoredTld(tld)) { continue; } const tldEscaped: string = tld.replace(".", ""); if (escapedWord.endsWith(tldEscaped) && escapedWord.length >= tldEscaped.length + 2) { const domain = `${escapedWord.substring(0, escapedWord.length - tldEscaped.length)}.${tld}`; if (!domain.includes("-.")) { const output = this.newDomain(domain); this.history.push(output); domains.push(output); } } } if (domains.length) { resolve(domains); } reject(new Error("Could not generate a valid domain")); }) .catch(reject); }); } }