1
0
mirror of https://github.com/thedaviddelta/lingva-scraper.git synced 2025-10-05 15:52:40 +02:00

General refactor and simple translation implementation

This commit is contained in:
David
2022-06-10 21:05:58 +02:00
parent 3076190853
commit e0847c1980
18 changed files with 431 additions and 140 deletions

View File

@@ -34,6 +34,7 @@
],
"dependencies": {
"axios": "^0.27.2",
"cheerio": "^1.0.0-rc.11",
"user-agents": "^1.0.1033"
},
"devDependencies": {

View File

@@ -1,13 +1,28 @@
import { mapGoogleCode, LangCode } from "./utils/language";
import request, { Endpoint } from "./utils/request";
export const getAudio = (lang: LangCode<"target">, text: string) => {
/**
* Retrieves an audio buffer of the given text in the given language
* @param lang - The code of the language to be used as the TTS voice
* @param text - The text to be converted into speech
* @param [isSlow=false] - Whether the audio should be slowed down or not
* @returns An array of numbers that represents a {@link Uint8Array} of the audio buffer
*/
export const getAudio = async (
lang: LangCode<"target">,
text: string,
isSlow: boolean = false
): Promise<number[] | null> => {
const parsedLang = mapGoogleCode(lang);
const lastSpace = text.lastIndexOf(" ", 200);
const slicedText = text.slice(0, text.length > 200 && lastSpace !== -1 ? lastSpace : 200);
const encodedText = encodeURIComponent(slicedText);
const textLength = slicedText.length;
const speed = isSlow ? 0.1 : 1;
return request(Endpoint.AUDIO)
.with({ lang: parsedLang, text: slicedText })
.with({ lang: parsedLang, text: encodedText, textLength, speed })
.doing(({ data }) => data ? Array.from(new Uint8Array(data)) : null);
};

View File

@@ -1,4 +1,5 @@
export * from "./info";
export * from "./simple";
export * from "./audio";
export * from "./translation";
export * from "./utils/language";
export * from "./utils/interfaces";

51
src/info.ts Normal file
View File

@@ -0,0 +1,51 @@
import { mapGoogleCode, LangCode } from "./utils/language";
import request, { Endpoint } from "./utils/request";
import * as parse from "./utils/parse";
import { Boilerplate, Data } from "./utils/types";
import { TranslationInfo } from "./utils/interfaces";
/**
* Retrieves the full translation information, including the translated text and, optionally, other relevant data
* @param source - The code of the language to translate from
* @param target - The code of the language to translate to
* @param query - The text to be translated
* @returns An element with all the information, as defined in {@link TranslationInfo}
*/
export const getTranslationInfo = async (
source: LangCode<"source">,
target: LangCode<"target">,
query: string
): Promise<TranslationInfo | null> => {
const parsedSource = mapGoogleCode(source);
const parsedTarget = mapGoogleCode(target);
const reqData = JSON.stringify([[query, parsedSource, parsedTarget, true], [null]]);
const reqBoilerplate = JSON.stringify([[["MkEWBc", reqData, null, "generic"]]]);
const body = "f.req=" + encodeURIComponent(reqBoilerplate);
return request(Endpoint.INFO)
.with({ body })
.doing(({ data }) => {
const resBoilerplate: Boilerplate = JSON.parse(data?.split("\n")?.[3]);
const resData: Data = JSON.parse(resBoilerplate?.[0]?.[2]);
if (!resData)
return;
const translation = parse.translation(resData);
if (!translation)
return;
return parse.undefinedFields({
translation,
detectedSource: parse.detected(resData),
pronunciation: {
query: parse.pronunciation.query(resData),
translation: parse.pronunciation.translation(resData)
},
definitions: parse.list.definitions(resData),
examples: parse.list.examples(resData),
similar: parse.list.similar(resData),
extraTranslations: parse.list.translations(resData),
});
});
};

36
src/simple.ts Normal file
View File

@@ -0,0 +1,36 @@
import cheerio from "cheerio";
import { mapGoogleCode, LangCode } from "./utils/language";
import request, { Endpoint } from "./utils/request";
/**
* Retrieves the translation text using a legacy implementation
* @param source - The code of the language to translate from
* @param target - The code of the language to translate to
* @param query - The text to be translated
* @returns A single {@link string} with the translated text
*/
export const getSimpleTranslation = async (
source: LangCode<"source">,
target: LangCode<"target">,
query: string
): Promise<string | null> => {
const parsedSource = mapGoogleCode(source);
const parsedTarget = mapGoogleCode(target);
const encodedQuery = encodeURIComponent(query);
if (encodedQuery.length > 7500)
return null;
return request(Endpoint.SIMPLE)
.with({ source: parsedSource, target: parsedTarget, query: encodedQuery })
.doing(({ data }) => {
if (!data)
return;
const translation = cheerio.load(data)(".result-container").text()?.trim();
return translation && !translation.includes("#af-error-page")
? translation
: null;
});
};

View File

@@ -1,44 +0,0 @@
import { mapGoogleCode, LangCode } from "./utils/language";
import request, { Endpoint } from "./utils/request";
import * as parse from "./utils/parse";
import { Boilerplate, Info } from "./utils/types";
import { Translation } from "./utils/interfaces";
export const getTranslation = (
source: LangCode<"source">,
target: LangCode<"target">,
query: string
): Promise<Translation | null> => {
const parsedSource = mapGoogleCode(source);
const parsedTarget = mapGoogleCode(target);
const reqInfo = JSON.stringify([[query, parsedSource, parsedTarget, true], [null]]);
const reqBoilerplate = JSON.stringify([[["MkEWBc", reqInfo, null, "generic"]]]);
const body = "f.req=" + encodeURIComponent(reqBoilerplate);
return request(Endpoint.TRANSLATION)
.with({ body })
.doing(({ data }) => {
const boilerplate: Boilerplate = JSON.parse(data?.split("\n")?.[3]);
const info: Info = JSON.parse(boilerplate?.[0]?.[2]);
if (!info)
return;
const translation = parse.translation(info);
if (!translation)
return;
return parse.undefinedFields({
translation,
detectedSource: parse.detected(info),
pronunciation: {
query: parse.pronunciation.query(info),
translation: parse.pronunciation.translation(info)
},
definitions: parse.list.definitions(info),
examples: parse.list.examples(info),
similar: parse.list.similar(info),
extraTranslations: parse.list.translations(info),
});
});
};

View File

@@ -20,13 +20,13 @@ interface ExtraTranslationsGroup {
}[]
}
export interface Translation {
export interface TranslationInfo {
translation: string,
detectedSource?: LangCode<"source">,
pronunciation: {
query?: string,
translation?: string
}
},
definitions: DefinitionsGroup[],
examples: string[],
similar: string[],

View File

@@ -19,6 +19,12 @@ export type LangCodeGoogle<T extends LangCode | LangType = LangCode> =
const isKeyOf = <T extends object>(obj: T) => (key: keyof any): key is keyof T => key in obj;
/**
* Changes invalid languages for a certain language type with the proper replacement
* @param langType - The type of language to check on
* @param langCode - A *Lingva* language code
* @returns A proper *Lingva* code for that language type
*/
export const replaceExceptedCode = <T extends LangType>(langType: T, langCode: LangCode) => {
const langExceptions = exceptions[langType];
const finalCode = isKeyOf(langExceptions)(langCode)
@@ -27,6 +33,11 @@ export const replaceExceptedCode = <T extends LangType>(langType: T, langCode: L
return finalCode as LangCode<T>;
};
/**
* Maps a *Lingva* language code to a *Google* one
* @param langCode - A *Lingva* language code
* @returns The proper *Google* code for that language
*/
export const mapGoogleCode = <T extends LangCode>(langCode: T) => {
const reqMappings = mappings["request"];
const finalCode = isKeyOf(reqMappings)(langCode)
@@ -35,6 +46,11 @@ export const mapGoogleCode = <T extends LangCode>(langCode: T) => {
return finalCode as LangCodeGoogle<T>;
};
/**
* Maps a *Google* language code to a *Lingva* one
* @param langCode - A *Google* language code
* @returns The proper *Lingva* code for that language
*/
export const mapLingvaCode = <T extends LangType>(langCode: LangCodeGoogle<T>) => {
const resMappings = mappings["response"];
const finalCode = isKeyOf(resMappings)(langCode)

View File

@@ -1,27 +1,27 @@
import { Info } from "./types";
import { Data } from "./types";
import { mapLingvaCode } from "./language";
import { Translation } from "./interfaces";
import { TranslationInfo } from "./interfaces";
export const detected = ([source, target, detected, extra]: Info): Translation["detectedSource"] => {
export const detected = ([source, target, detected, extra]: Data): TranslationInfo["detectedSource"] => {
const code = detected ?? source?.[2] ?? target?.[3] ?? extra?.[8] ?? extra?.[5]?.[0]?.[0]?.[3];
return code ? mapLingvaCode<"source">(code) : undefined;
};
export const translation = ([, target]: Info): Translation["translation"] | undefined => (
export const translation = ([, target]: Data): TranslationInfo["translation"] | undefined => (
target?.[0]?.[0]?.[5]?.[0]?.[0] ?? target?.[0]?.[0]?.[5]?.[0]?.[4]?.[0]?.[0]
);
export const pronunciation = {
query: ([source]: Info): Translation["pronunciation"]["query"] => (
query: ([source]: Data): TranslationInfo["pronunciation"]["query"] => (
source?.[0] ?? undefined
),
translation: ([, target]: Info): Translation["pronunciation"]["translation"] => (
translation: ([, target]: Data): TranslationInfo["pronunciation"]["translation"] => (
target?.[0]?.[0]?.[1] ?? undefined
)
};
export const list = {
definitions: ({ 3: extra }: Info): Translation["definitions"] => (
definitions: ({ 3: extra }: Data): TranslationInfo["definitions"] => (
extra?.[1]?.[0]?.map(([type, defList]) => ({
type,
list: defList?.map(({ 0: definition, 1: example, 4: fieldWrapper, 5: synList }) => ({
@@ -34,13 +34,13 @@ export const list = {
})) ?? []
})) ?? []
),
examples: ({ 3: extra }: Info): Translation["examples"] => (
examples: ({ 3: extra }: Data): TranslationInfo["examples"] => (
extra?.[2]?.[0]?.map(([, item]) => item) ?? []
),
similar: ({ 3: extra }: Info): Translation["similar"] => (
similar: ({ 3: extra }: Data): TranslationInfo["similar"] => (
extra?.[3]?.[0] ?? []
),
translations: ({ 3: extra }: Info): Translation["extraTranslations"] => (
translations: ({ 3: extra }: Data): TranslationInfo["extraTranslations"] => (
extra?.[5]?.[0]?.map(([type, transList]) => ({
type,
list: transList?.map(([word, article, meanings, frequency]) => ({

View File

@@ -3,19 +3,27 @@ import UserAgent from "user-agents";
import { LangCodeGoogle } from "./language";
export const Endpoint = {
TRANSLATION: "translation",
INFO: "info",
SIMPLE: "simple",
AUDIO: "audio"
} as const;
type EndpointType = typeof Endpoint[keyof typeof Endpoint];
type Params = {
translation: {
body: string,
[Endpoint.INFO]: {
body: string
},
audio: {
[Endpoint.SIMPLE]: {
source: LangCodeGoogle<"source">,
target: LangCodeGoogle<"target">,
query: string
},
[Endpoint.AUDIO]: {
lang: LangCodeGoogle<"target">,
text: string
text: string,
textLength: number,
speed: number
}
};
@@ -26,9 +34,7 @@ const request = <T extends EndpointType>(
with: (
params: Params[T]
) => {
const promise = endpoint === "translation"
? fetchTranslation(params as Params["translation"])
: fetchAudio(params as Params["audio"]);
const promise = retrieve(endpoint, params);
return {
promise,
doing: <V>(
@@ -50,29 +56,47 @@ const isEmpty = (item: any) => (
!item || (typeof item === "object" && "length" in item && item.length <= 0)
);
const fetchTranslation = ({ body }: Params["translation"]) => (
axios.post(
"https://translate.google.com/_/TranslateWebserverUi/data/batchexecute?rpcids=MkEWBc&rt=c",
body,
{
headers: {
"User-Agent": new UserAgent().toString(),
"Content-Type": "application/x-www-form-urlencoded"
const retrieve = <T extends EndpointType>(endpoint: T, params: Params[T]) => {
if (endpoint === Endpoint.INFO) {
const { body } = params as Params[typeof Endpoint.INFO];
return axios.post(
"https://translate.google.com/_/TranslateWebserverUi/data/batchexecute?rpcids=MkEWBc&rt=c",
body,
{
headers: {
"User-Agent": new UserAgent().toString(),
"Content-Type": "application/x-www-form-urlencoded"
}
}
}
)
);
);
}
const fetchAudio = ({ lang, text }: Params["audio"]) => (
axios.get(
`https://translate.google.com/translate_tts?tl=${lang}&q=${encodeURIComponent(text)}&textlen=${text.length}&client=tw-ob`,
{
responseType: "arraybuffer",
headers: {
"User-Agent": new UserAgent().toString()
if (endpoint === Endpoint.SIMPLE) {
const { source, target, query } = params as Params[typeof Endpoint.SIMPLE];
return axios.get(
`https://translate.google.com/m?sl=${source}&tl=${target}&q=${query}`,
{
headers: {
"User-Agent": new UserAgent().toString()
}
}
}
)
);
);
}
if (endpoint === Endpoint.AUDIO) {
const { lang, text, textLength, speed } = params as Params[typeof Endpoint.AUDIO];
return axios.get(
`https://translate.google.com/translate_tts?tl=${lang}&q=${text}&textlen=${textLength}&speed=${speed}&client=tw-ob`,
{
responseType: "arraybuffer",
headers: {
"User-Agent": new UserAgent().toString()
}
}
);
}
throw new Error("Invalid endpoint");
};
export default request;

View File

@@ -4,7 +4,7 @@ export type Boilerplate = [
[
"wrb.fr",
"MkEWBc",
string, // info
string, // data
null,
null,
null,
@@ -22,14 +22,14 @@ export type Boilerplate = [
]
];
export type Info = [
InfoSource,
InfoTarget,
export type Data = [
DataSource,
DataTarget,
LangCodeGoogle<"source">, // source/detected
InfoExtra | undefined
DataExtra | undefined
];
type InfoSource = [
type DataSource = [
string | null, // pronunciation
[
null,
@@ -67,7 +67,7 @@ type InfoSource = [
]
];
type InfoTarget = [ // target
type DataTarget = [ // target
[
[
null,
@@ -100,7 +100,7 @@ type InfoTarget = [ // target
]
];
type InfoExtra = [
type DataExtra = [
string, // query
[ // definitions
[

View File

@@ -1,23 +1,26 @@
import { getAudio, LangCode } from "../src";
// @ts-ignore
import { expectFromEntries, expectFromWrong, sampleText } from "./testUtils";
const entries: [LangCode<"target">, string][] = [
type Entry = [LangCode<"target">, string];
const entries: Entry[] = [
["es", "hola"],
["ca", "gerd"],
["en", "impression"],
["zh", "早安"],
["zh_HANT", "早安"]
["zh_HANT", "早安"],
["ca", sampleText.large],
["ca", sampleText.huge]
];
it("returns audio buffer correctly", async () => (
Promise.all(entries.map((entry) => getAudio(...entry)))
.then(results => results.forEach(audio => {
expect(audio).not.toBeNull();
audio?.forEach(int => expect(int).toEqual(expect.any(Number)));
}))
it("returns audio buffer correctly", () => (
expectFromEntries(entries, getAudio, audio => {
expect(audio).not.toBeNull();
audio?.forEach(int => expect(int).toEqual(expect.any(Number)));
})
));
it("returns null on wrong params", async () => (
// @ts-ignore
getAudio("", "")
.then(audio => expect(audio).toBeNull())
it("returns null on wrong params", () => (
expectFromWrong(getAudio)
));

30
tests/info.test.ts Normal file
View File

@@ -0,0 +1,30 @@
import { getTranslationInfo, LangCode } from "../src";
// @ts-ignore
import { expectFromEntries, expectFromWrong, expectTruthyValues, sampleText } from "./testUtils";
type Entry = [LangCode<"source">, LangCode<"target">, string];
const entries: Entry[] = [
["es", "en", "hola"],
["es", "en", "convención"],
["auto", "en", "prueba"],
["auto", "es", "win"],
["zh", "en", "早安"],
["ca", "es", sampleText.large]
];
it("returns translation info correctly", () => (
expectFromEntries(entries, getTranslationInfo, info => {
expect(info).not.toBeNull();
info && expectTruthyValues(info);
})
));
it("returns null on wrong params", () => (
expectFromWrong(getTranslationInfo)
));
it("returns null on huge text", () => (
getTranslationInfo("ca", "es", sampleText.huge)
.then(info => expect(info).toBeNull())
));

29
tests/simple.test.ts Normal file
View File

@@ -0,0 +1,29 @@
import { getSimpleTranslation, LangCode } from "../src";
// @ts-ignore
import { expectFromEntries, expectFromWrong, sampleText } from "./testUtils";
type Entry = [LangCode<"source">, LangCode<"target">, string];
const entries: Entry[] = [
["es", "en", "hola"],
["es", "en", "convención"],
["auto", "en", "prueba"],
["auto", "es", "win"],
["zh", "en", "早安"],
["ca", "es", sampleText.large]
];
it("returns translated text correctly", () => (
expectFromEntries(entries, getSimpleTranslation, trans => {
expect(trans).not.toBeNull();
})
));
it("returns null on wrong params", () => (
expectFromWrong(getSimpleTranslation)
));
it("returns null on huge text", () => (
getSimpleTranslation("ca", "es", sampleText.huge)
.then(trans => expect(trans).toBeNull())
));

32
tests/testUtils.ts Normal file
View File

@@ -0,0 +1,32 @@
import { EOL } from "node:os";
import tirant from "./tirant.json";
export const expectFromEntries = <E extends any[], R>(
entries: E[],
runCb: (...params: E) => Promise<R>,
expectCb: (result: R) => any
): Promise<void> => (
Promise.all(entries.map(entry => runCb(...entry)))
.then(results => results.forEach(expectCb))
);
export const expectFromWrong = async <E extends any[]>(
callback: (...params: E) => Promise<any>
): Promise<void> => (
callback(...Array(10).fill("") as E)
.then(result => expect(result).toBeNull())
);
export const expectTruthyValues = (obj: object): void => {
const list = Array.isArray(obj) ? obj : Object.values(obj);
list.forEach(value =>
typeof value === "object"
? expectTruthyValues(value)
: expect(value).toBeTruthy()
);
};
export const sampleText = {
large: tirant.prologue,
huge: tirant.chapters.join(EOL)
};

10
tests/tirant.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,32 +0,0 @@
import { getTranslation, LangCode } from "../src";
const entries: [LangCode<"source">, LangCode<"target">, string][] = [
["es", "en", "hola"],
["es", "en", "convención"],
["auto", "en", "prueba"],
["auto", "es", "win"],
["zh", "en", "早安"]
];
export const expectNoUndefined = (obj: object): void => {
const list = Array.isArray(obj) ? obj : Object.values(obj);
list.forEach(value =>
typeof value === "object"
? expectNoUndefined(value)
: expect(value).toBeTruthy()
);
};
it("returns translation info correctly", async () => (
Promise.all(entries.map((entry) => getTranslation(...entry)))
.then(results => results.forEach(trans => {
expect(trans).not.toBeNull();
trans && expectNoUndefined(trans);
}))
));
it("returns null on wrong params", async () => (
// @ts-ignore
getTranslation("", "", "")
.then(trans => expect(trans).toBeNull())
));

119
yarn.lock
View File

@@ -774,6 +774,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -861,6 +866,32 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
cheerio-select@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
dependencies:
boolbase "^1.0.0"
css-select "^5.1.0"
css-what "^6.1.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.0.1"
cheerio@^1.0.0-rc.11:
version "1.0.0-rc.11"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.11.tgz#1be84be1a126958366bcc57a11648cd9b30a60c2"
integrity sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag==
dependencies:
cheerio-select "^2.1.0"
dom-serializer "^2.0.0"
domhandler "^5.0.3"
domutils "^3.0.1"
htmlparser2 "^8.0.1"
parse5 "^7.0.0"
parse5-htmlparser2-tree-adapter "^7.0.0"
tslib "^2.4.0"
ci-info@^3.2.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.1.tgz#58331f6f472a25fe3a50a351ae3052936c2c7f32"
@@ -942,6 +973,22 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-select@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
dependencies:
boolbase "^1.0.0"
css-what "^6.1.0"
domhandler "^5.0.2"
domutils "^3.0.1"
nth-check "^2.0.1"
css-what@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
debug@^4.1.0, debug@^4.1.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@@ -989,6 +1036,36 @@ docopt@~0.6.2:
resolved "https://registry.yarnpkg.com/docopt/-/docopt-0.6.2.tgz#b28e9e2220da5ec49f7ea5bb24a47787405eeb11"
integrity sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
domutils@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.1"
dot-json@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/dot-json/-/dot-json-1.2.2.tgz#7d35abece4aa22aa75a761388953f98495401bcc"
@@ -1013,6 +1090,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
entities@^4.2.0, entities@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.0.tgz#62915f08d67353bb4eb67e3d62641a4059aec656"
integrity sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -1191,6 +1273,16 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
htmlparser2@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010"
integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
domutils "^3.0.1"
entities "^4.3.0"
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
@@ -1838,6 +1930,13 @@ npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
nth-check@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
dependencies:
boolbase "^1.0.0"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -1881,6 +1980,21 @@ parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parse5-htmlparser2-tree-adapter@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
dependencies:
domhandler "^5.0.2"
parse5 "^7.0.0"
parse5@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.0.0.tgz#51f74a5257f5fcc536389e8c2d0b3802e1bfa91a"
integrity sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==
dependencies:
entities "^4.3.0"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -2193,6 +2307,11 @@ ts-jest@^28.0.3:
semver "7.x"
yargs-parser "^20.x"
tslib@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
type-detect@4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"