├── .gitignore ├── .gitattributes ├── src ├── public │ ├── logo.png │ ├── favicon.ico │ └── index.html ├── pagepool.ts ├── app.ts └── parser.ts ├── .vscode └── settings.json ├── Dockerfile ├── deno.json ├── LICENSE ├── README.md └── deno.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/songkeys/translateer/HEAD/src/public/logo.png -------------------------------------------------------------------------------- /src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/songkeys/translateer/HEAD/src/public/favicon.ico -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": "always", 6 | "source.organizeImports": "always" 7 | }, 8 | "editor.defaultFormatter": "denoland.vscode-deno", 9 | "deno.lint": true, 10 | "[typescript][javascript][json][markdown][html]": { 11 | "editor.defaultFormatter": "denoland.vscode-deno" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM denoland/deno:latest 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | wget \ 5 | gnupg \ 6 | ca-certificates \ 7 | apt-transport-https \ 8 | chromium \ 9 | chromium-driver \ 10 | xvfb \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | ENV CHROME_BIN=/usr/bin/chromium 14 | 15 | WORKDIR /app 16 | 17 | COPY . . 18 | 19 | RUN deno install 20 | 21 | # Run the app 22 | CMD ["deno", "run", "-A", "src/app.ts"] 23 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "start": "deno run -A src/app.ts" 4 | }, 5 | "imports": { 6 | "@std/http": "jsr:@std/http@^1.0.14", 7 | "@std/path": "jsr:@std/path@^1.0.8", 8 | "puppeteer": "npm:puppeteer@24.6.1", 9 | "puppeteer-extra": "npm:puppeteer-extra@3.3.6", 10 | "puppeteer-extra-plugin-stealth": "npm:puppeteer-extra-plugin-stealth@2.11.2", 11 | "puppeteer-real-browser": "npm:puppeteer-real-browser@^1.4.2" 12 | }, 13 | "compilerOptions": { 14 | "lib": ["deno.ns", "dom"] 15 | }, 16 | "nodeModulesDir": "auto", 17 | "fmt": { 18 | "useTabs": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Songkeys 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Translateer 2 | 3 | An unlimited free Google Translate API using Puppeteer. 4 | 5 | > This service is provided to the public for **educational purposes only**. 6 | 7 | ## Demo and Usage 8 | 9 | Try it out: 10 | 11 | ```bash 12 | curl 'https://t.song.work/api?text=hello&from=en&to=zh-CN' 13 | ``` 14 | 15 | Visit to see more usage. 16 | 17 | **This free demo can only serve 5 concurrent requests.** It does not collect any 18 | data. 19 | 20 | ## Self-Hosted 21 | 22 | ### Option 1: Serve with Docker (Recommended) 23 | 24 | 1. Clone the repository 25 | 26 | ```bash 27 | git clone https://github.com/songkeys/translateer.git 28 | ``` 29 | 30 | 2. Build and run Dockerfile 31 | 32 | ```bash 33 | docker build -t translateer . 34 | docker run -d -p 8999:8999 translateer 35 | ``` 36 | 37 | ### Option 2: Serve Locally 38 | 39 | 1. Clone the repository 40 | 41 | ```bash 42 | git clone https://github.com/songkeys/translateer.git 43 | ``` 44 | 45 | 2. Install dependencies and build 46 | 47 | ```bash 48 | deno install 49 | ``` 50 | 51 | 3. Run the server 52 | 53 | ```bash 54 | deno task start 55 | ``` 56 | 57 | ### Environment Variables 58 | 59 | See the markdown table below: 60 | 61 | | Variable | Description | Default | 62 | | ------------ | ------------------------------------------- | ------- | 63 | | `PORT` | Port to listen on | `8999` | 64 | | `PAGE_COUNT` | Number of browser pages to hold for speedup | `5` | 65 | 66 | ## Raycast Extension 67 | 68 | An easy-to-use [Raycast](https://www.raycast.com) extension is provided. Check 69 | [songkeys/raycast-extension#Translateer](https://github.com/songkeys/raycast-extension#translateer) 70 | for more details. 71 | 72 | ![raycast-extension-preview](https://user-images.githubusercontent.com/22665058/142718320-871b0c71-7e30-422a-889d-51d0bc6dcf88.png) 73 | -------------------------------------------------------------------------------- /src/pagepool.ts: -------------------------------------------------------------------------------- 1 | import { connect } from "puppeteer-real-browser"; 2 | 3 | export type Browser = Awaited>["browser"]; 4 | export type Page = Awaited>[number]; 5 | 6 | export let pagePool: PagePool; 7 | 8 | export default class PagePool { 9 | private _pages: Page[] = []; 10 | private _pagesInUse: Page[] = []; 11 | private _browser!: Browser; 12 | 13 | constructor(private pageCount: number = 5) { 14 | pagePool = this; 15 | } 16 | 17 | public async init() { 18 | await this._initBrowser(); 19 | await this._initPages(); 20 | 21 | // refresh pages every 1 hour to keep alive 22 | this._resetInterval(60 * 60 * 1000); 23 | } 24 | 25 | public getPage() { 26 | const page = this._pages.pop(); 27 | if (!page) { 28 | return undefined; 29 | } 30 | this._pagesInUse.push(page); 31 | return page; 32 | } 33 | 34 | public releasePage(page: Page) { 35 | const index = this._pagesInUse.indexOf(page); 36 | if (index === -1) { 37 | return; 38 | } 39 | this._pagesInUse.splice(index, 1); 40 | this._pages.push(page); 41 | } 42 | 43 | public async close() { 44 | await this._browser.close(); 45 | } 46 | 47 | private async _initBrowser() { 48 | const { browser } = await connect({ 49 | headless: false, 50 | args: [ 51 | "--no-sandbox", 52 | "--disable-setuid-sandbox", 53 | "--disable-dev-shm-usage", 54 | ], 55 | customConfig: {}, 56 | turnstile: true, 57 | connectOption: {}, 58 | disableXvfb: false, 59 | ignoreAllFlags: false, 60 | }); 61 | this._browser = browser; 62 | console.log("browser launched"); 63 | } 64 | 65 | private async _initPages() { 66 | this._pages = await Promise.all( 67 | Array(this.pageCount) 68 | .fill(null) 69 | .map(async (_, i) => { 70 | const page = await this._browser.newPage(); 71 | await this._setupPage(page, i); 72 | return page; 73 | }), 74 | ); 75 | } 76 | 77 | private async _setupPage(page: Page, index: number) { 78 | await page.setCacheEnabled(false); 79 | await page.setRequestInterception(true); 80 | page.on("request", (req) => { 81 | if (["image", "stylesheet", "font"].includes(req.resourceType())) { 82 | req.abort(); 83 | } else { 84 | req.continue(); 85 | } 86 | }); 87 | 88 | console.log(`page ${index} created`); 89 | await page.goto("https://translate.google.com/details", { 90 | waitUntil: "networkidle2", 91 | }); 92 | console.log(`page ${index} loaded`); 93 | 94 | await this._handlePrivacyConsent(page, index); 95 | console.log( 96 | `page ${index} ready (${this._pages.length + 1}/${this.pageCount})`, 97 | ); 98 | } 99 | 100 | private async _handlePrivacyConsent(page: Page, index: number) { 101 | try { 102 | const btnSelector = 'button[aria-label="Reject all"]'; 103 | await page.waitForSelector(btnSelector, { timeout: 1000 }); 104 | await page.click(btnSelector); 105 | console.log(`page ${index} privacy consent rejected`); 106 | } catch { 107 | console.log(`page ${index} privacy consent not found`); 108 | } 109 | } 110 | 111 | private _resetInterval(ms: number) { 112 | setInterval(async () => { 113 | this._pagesInUse = []; 114 | this._pages = []; 115 | await this._browser.close(); 116 | await this._initBrowser(); 117 | await this._initPages(); 118 | }, ms); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { serveDir } from "@std/http"; 2 | import { resolve } from "@std/path"; 3 | import PagePool, { pagePool } from "./pagepool.ts"; 4 | import { parsePage } from "./parser.ts"; 5 | 6 | const { PAGE_COUNT = "5", PORT = "8999" } = Deno.env.toObject(); 7 | 8 | console.log("initializing pages..."); 9 | 10 | try { 11 | await new PagePool(parseInt(PAGE_COUNT, 10)).init(); 12 | } catch (e) { 13 | console.log("Failed to initialize pages"); 14 | console.error(e); 15 | Deno.exit(1); 16 | } 17 | 18 | console.log("ready"); 19 | 20 | // on exit, close the page pool 21 | Deno.addSignalListener("SIGINT", async () => { 22 | console.log("SIGINT"); 23 | await pagePool.close(); 24 | Deno.exit(0); 25 | }); 26 | 27 | Deno.serve({ port: parseInt(PORT, 10) }, async (req) => { 28 | try { 29 | const url = new URL(req.url); 30 | 31 | if (url.pathname === "/api") { 32 | const options = { 33 | text: url.searchParams.get("text"), 34 | from: url.searchParams.get("from") ?? "auto", 35 | to: url.searchParams.get("to") ?? "zh-CN", 36 | lite: url.searchParams.get("lite") === "true", 37 | ...(await req.json().catch(() => ({}))), 38 | }; 39 | 40 | const { text, from, to, lite } = options; 41 | 42 | if (!text) { 43 | serverLog(req, 400); 44 | return new Response( 45 | JSON.stringify({ error: 1, message: "text is required" }), 46 | { 47 | status: 400, 48 | headers: { 49 | "Content-Type": "application/json; charset=utf-8", 50 | }, 51 | }, 52 | ); 53 | } 54 | 55 | const page = pagePool.getPage(); 56 | if (!page) { 57 | serverLog(req, 400); 58 | return new Response( 59 | JSON.stringify({ error: 1, message: "No available pages" }), 60 | { 61 | status: 400, 62 | headers: { 63 | "Content-Type": "application/json; charset=utf-8", 64 | }, 65 | }, 66 | ); 67 | } 68 | 69 | try { 70 | const result = await parsePage(page, { text, from, to, lite }); 71 | serverLog(req, 200); 72 | return new Response(JSON.stringify(result), { 73 | status: 200, 74 | headers: { 75 | "Content-Type": "application/json; charset=utf-8", 76 | }, 77 | }); 78 | } catch (e) { 79 | serverLog(req, 500); 80 | console.error(e); 81 | return new Response( 82 | JSON.stringify({ error: 1, message: "Internal Server Error" }), 83 | { 84 | status: 500, 85 | headers: { 86 | "Content-Type": "application/json; charset=utf-8", 87 | }, 88 | }, 89 | ); 90 | } finally { 91 | pagePool.releasePage(page); 92 | } 93 | } 94 | 95 | return serveDir(req, { 96 | fsRoot: resolve(Deno.cwd(), "src", "public"), 97 | }); 98 | } catch (e) { 99 | serverLog(req, 500); 100 | console.error(e); 101 | return new Response( 102 | JSON.stringify({ 103 | error: 1, 104 | message: e instanceof Error ? e.message : "Internal Server Error", 105 | }), 106 | { 107 | status: 500, 108 | headers: { 109 | "Content-Type": "application/json; charset=utf-8", 110 | }, 111 | }, 112 | ); 113 | } 114 | }); 115 | 116 | function serverLog(req: Request, status: number) { 117 | const d = new Date().toISOString(); 118 | const dateFmt = `[${d.slice(0, 10)} ${d.slice(11, 19)}]`; 119 | const url = new URL(req.url); 120 | const s = `${dateFmt} [${req.method}] ${url.pathname}${url.search} ${status}`; 121 | // deno-lint-ignore no-console 122 | console.debug(s); 123 | } 124 | -------------------------------------------------------------------------------- /src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 18 | Translateer - Google Translate API 19 | 20 | 21 |
24 |

25 | 26 | Translateer 27 |

28 | 29 |
A free Google Translate API using Puppeteer.
30 | 31 |

Usage

32 |
33 |

Parameters

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 |
NameTypeDescriptionDefault
textstringThe text to translate.[Required]
fromstringThe language code of the source text.auto
tostringThe language code of the target text.zh-CN
liteboolean 66 | If true, only return result for the translated text. 67 | false
72 | 73 |
74 | The language code is in ISO-639-1 format. See all available code listed 75 | 80 | here. 81 |
82 | 83 |

Example Response

84 |
89 | Loading 90 |
91 | 92 |

Disclaimer

93 | 94 |
    95 |
  • 96 | This service is not provided by the official Google Translate. It is 97 | provided as a service to the public for 98 | educational purposes only. 99 |
  • 100 |
  • 101 | This service has no guarantee that the translation will be accurate. 102 | The service is not responsible for any errors or omissions. The 103 | service is provided as is and 104 | without warranty. 105 |
  • 106 |
  • You can also self-host this service. See the source code below.
  • 107 |
108 | 109 |

Source Code

110 |

111 | GitHub: 112 | 113 | songkeys/translateer 114 | 115 |

116 | 117 |

Sponsor

118 |

119 | If you find this project useful, please consider sponsoring me. It will 120 | be used to maintain the service and improve the quality. 121 |

122 | 129 |
130 | 131 | 134 | 137 | 148 | 149 | 174 | 175 |
176 |
177 |

178 | Made by Songkeys @ 2021 179 |

180 |
181 |
182 | 183 | 184 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "./pagepool.ts"; 2 | 3 | type IExamples = string[]; 4 | 5 | type IDefinitions = Record< 6 | string, 7 | { 8 | definition: string; 9 | example?: string; 10 | labels?: string[]; 11 | synonyms?: Record; 12 | }[] 13 | >; 14 | 15 | type ITranslations = Record< 16 | string, 17 | { 18 | translation: string; 19 | reversedTranslations: string[]; 20 | frequency: string; 21 | }[] 22 | >; 23 | 24 | export const parsePage = async ( 25 | page: Page, 26 | { 27 | text, 28 | from, 29 | to, 30 | lite, 31 | }: { 32 | text: string; 33 | from: string; 34 | to: string; 35 | lite: boolean; 36 | }, 37 | ) => { 38 | // click clear button 39 | await page.$eval( 40 | "button[aria-label='Clear source text']", 41 | (btn) => (btn as HTMLButtonElement).click(), 42 | ); 43 | 44 | // switch source and target language 45 | await page.evaluate( 46 | (fromSelector, toSelector) => { 47 | const fromLangs = Array.from( 48 | document.querySelectorAll(fromSelector), 49 | ); 50 | if (fromLangs.length === 0) { 51 | throw new Error(`No from language found, ${fromSelector}`); 52 | } 53 | 54 | const toLangs = Array.from( 55 | document.querySelectorAll(toSelector), 56 | ); 57 | if (toLangs.length === 0) { 58 | throw new Error(`No to language found, ${toSelector}`); 59 | } 60 | 61 | const isInRecentScope = (el: HTMLElement) => 62 | (el.parentElement?.firstChild as HTMLElement)?.innerText === 63 | "Recent languages"; 64 | 65 | // (all)? (all)? ? ? 66 | // from 67 | // (all)? (all)? ? ? 68 | // to 69 | let from = fromLangs[0]!; 70 | let to = toLangs[0]!; 71 | 72 | // check from 73 | if (isInRecentScope(from)) { 74 | // recent all 75 | // from 76 | from = fromLangs[1]!; 77 | } 78 | 79 | // check to 80 | if (isInRecentScope(to)) { 81 | // recent all ? ? 82 | // to 83 | to = toLangs[2]!; 84 | if (isInRecentScope(to)) { 85 | // recent all recent all 86 | // to 87 | to = toLangs[3]!; 88 | } 89 | } else { 90 | // all ? ? ? 91 | // to 92 | to = toLangs[1]!; 93 | if (isInRecentScope(to)) { 94 | // all recent all \ 95 | // to 96 | to = toLangs[2]!; 97 | } 98 | } 99 | 100 | if (from.getAttribute("aria-selected") !== "true") { 101 | from.click(); 102 | } 103 | if (to.getAttribute("aria-selected") !== "true") { 104 | to.click(); 105 | } 106 | }, 107 | from === "auto" 108 | ? `button[data-language-code='auto']` 109 | : `div[data-language-code='${from}']`, 110 | `div[data-language-code='${to}']`, 111 | ); 112 | 113 | // type text 114 | const textareaSelector = "textarea[aria-label='Source text']"; 115 | await page.$eval( 116 | textareaSelector, 117 | ( 118 | textarea, 119 | text, 120 | ) => ((textarea as HTMLTextAreaElement).value = text as string), 121 | text, 122 | ); 123 | await page.type(textareaSelector, " "); 124 | 125 | // translating... 126 | let result = ""; 127 | let pronunciation = ""; 128 | do { 129 | // const targetSelector = `span[data-language-for-alternatives=${to}]`; 130 | const targetSelector = `span[lang=${to}]`; 131 | const targetTextSelector = `span[lang=${to}] > span > span`; 132 | await page.waitForSelector(targetSelector); 133 | 134 | // get translated text 135 | result += await page.evaluate( 136 | (targetSelector, targetTextSelector) => 137 | Array.from( 138 | document 139 | .querySelector(targetSelector)! 140 | .querySelectorAll(targetTextSelector)!, 141 | ) 142 | .map((s) => s.innerText!.replace(/[\u200B-\u200D\uFEFF]/g, "")) 143 | .join(""), // remove zero-width space 144 | targetSelector, 145 | targetTextSelector, 146 | ); 147 | 148 | // get pronunciation 149 | pronunciation += (await page.evaluate(() => 150 | document 151 | .querySelector('div[data-location="2"] > div') 152 | ?.innerText?.replace(/[\u200B-\u200D\uFEFF]/g, "") 153 | )) || ""; 154 | 155 | // get next page 156 | const shouldContinue = await page.evaluate(() => { 157 | const next = document.querySelector('button[aria-label="Next"]'); 158 | const pseudoNext = getComputedStyle( 159 | document.querySelector('button[aria-label="Next"] > div')!, 160 | "::before", 161 | ); 162 | const hasNext = next && pseudoNext.width.endsWith("px") && 163 | pseudoNext.width !== "0px"; 164 | const isLastPage = next?.hasAttribute("disabled"); 165 | const shouldContinue = Boolean(hasNext && !isLastPage); 166 | return shouldContinue; 167 | }); 168 | 169 | if (shouldContinue) { 170 | // await network idle first 171 | const xhr = page.waitForResponse((r) => { 172 | return r 173 | .url() 174 | .startsWith( 175 | "https://translate.google.com/_/TranslateWebserverUi/data/batchexecute", 176 | ); 177 | }); 178 | 179 | await page.evaluate(() => { 180 | const next = document.querySelector( 181 | 'button[aria-label="Next"]', 182 | )!; 183 | next.click(); 184 | }); 185 | 186 | await xhr; 187 | } else { 188 | break; 189 | } 190 | } while (true); 191 | 192 | // get from 193 | // const fromISO = await page.evaluate(() => 194 | // document 195 | // .querySelector("div[data-original-language]")! 196 | // .getAttribute("data-original-language") 197 | // ); 198 | // [TODO] when it's auto, we need to get real "from" otherwise the "examples" won't work 199 | // here is the script to get all the iso codes and their names 200 | // [...window.document.querySelectorAll("div[data-language-code]")].map(e => ({ 201 | // code: e.getAttribute("data-language-code"), 202 | // name: e.innerText.trim(), 203 | // })).filter(e => e.code && !e.code.includes("history")) 204 | 205 | // get did you mean 206 | const fromDidYouMean = await page.evaluate(() => { 207 | const didYouMeanBlock = document.querySelector("html-blob"); 208 | const hasDidYouMean = ["Did you mean:", "Showing translation for"].some( 209 | (t) => 210 | didYouMeanBlock?.parentElement?.parentElement?.innerHTML.includes(t), 211 | ); 212 | 213 | return hasDidYouMean ? didYouMeanBlock?.innerText : undefined; 214 | }); 215 | 216 | // get suggestions 217 | const fromSuggestions = lite || from === "auto" // auto lang doesn't have suggestions 218 | ? undefined 219 | : await page.evaluate(() => { 220 | const sgsBlocks = Array.from( 221 | document.querySelectorAll('ul[role="listbox"] > li'), 222 | ); 223 | return sgsBlocks.length === 0 ? undefined : sgsBlocks.map((b) => { 224 | return { 225 | text: b.children[0].textContent!.replace( 226 | /[\u200B-\u200D\uFEFF]/g, 227 | "", 228 | ), 229 | translation: b.children[1].textContent!.replace( 230 | /[\u200B-\u200D\uFEFF]/g, 231 | "", 232 | ), 233 | }; 234 | }); 235 | }); 236 | 237 | // get from pronunciation 238 | const fromPronunciation = (await page.evaluate(() => 239 | document 240 | .querySelector('div[data-location="1"] > div') 241 | ?.innerText?.replace(/[\u200B-\u200D\uFEFF]/g, "") 242 | )) || undefined; 243 | 244 | const noDetails = await page.evaluate(() => { 245 | return document 246 | .querySelector("c-wiz[role='complementary'] > div > div") 247 | ?.innerText?.startsWith("No details found for"); 248 | }); 249 | 250 | // get examples 251 | await page.waitForSelector("html-blob", { timeout: 100 }).catch(() => {}); 252 | 253 | // get definitions 254 | const definitions = lite || noDetails 255 | ? undefined 256 | : await page.evaluate(() => { 257 | const ret: IDefinitions = {}; 258 | 259 | if ( 260 | !document 261 | .querySelector( 262 | "c-wiz[role='complementary'] > div > c-wiz > div > div:nth-child(3) > div > div > div", 263 | ) 264 | ?.innerText.includes("Definitions of") 265 | ) { 266 | return ret; 267 | } 268 | 269 | const definitionalBlocks = Array.from( 270 | document.querySelectorAll( 271 | "c-wiz[role='complementary'] > div > c-wiz > div > div:nth-child(3) > div > div > div > div", 272 | ), 273 | ); 274 | 275 | let blockClassName = undefined; 276 | for ( 277 | let i = 0, 278 | currentPos = "unknown", 279 | currentLabels: string[] | undefined; 280 | i < definitionalBlocks.length; 281 | ++i 282 | ) { 283 | const isHiddenBlock = 284 | definitionalBlocks[i].getAttribute("role") === "presentation"; 285 | const block = isHiddenBlock 286 | ? definitionalBlocks[i].children[0] 287 | : definitionalBlocks[i]; 288 | 289 | const isButtonBlock = block.children[0].tagName === "BUTTON"; // Show all button 290 | if (isButtonBlock) { 291 | continue; 292 | } 293 | 294 | const isPosBlock = block.children[0].childElementCount === 0; // a text block 295 | if (isPosBlock) { 296 | currentPos = block.children[0].textContent!.toLowerCase(); 297 | if (currentPos.includes("expand")) { 298 | continue; 299 | } 300 | ret[currentPos] = []; 301 | currentLabels = undefined; // reset labels 302 | } else { 303 | // parse definition block 304 | const def: IDefinitions[string][number] = { definition: "" }; 305 | if (!blockClassName) { 306 | blockClassName = block.className; 307 | } else if (block.className !== blockClassName) { 308 | continue; 309 | } 310 | const leftBlock = block.children[0]; // its children should be number or nothing 311 | const rightBlock = block.children[1]; // its children should be the definition div or label div 312 | const isRightBlockLabel = leftBlock.childElementCount === 0; 313 | if (isRightBlockLabel) { 314 | currentLabels = [rightBlock.textContent!.toLowerCase()]; // this label should be the following blocks' labels 315 | continue; 316 | } else { 317 | // definition block 318 | 319 | // check the previous labels first 320 | if (currentLabels) { 321 | def.labels = currentLabels; 322 | } 323 | 324 | const blocks = Array.from(rightBlock.children); 325 | 326 | // the leading block could be (local) labels 327 | const hasLabels = blocks[0].childElementCount >= 1; 328 | if (hasLabels) { 329 | def.labels = Array.from(blocks[0].children).map( 330 | (b) => b.textContent!, 331 | ); 332 | blocks.shift(); 333 | } 334 | 335 | // there must be a definition 336 | def.definition = blocks[0].textContent!; 337 | blocks.shift(); 338 | 339 | // there may be some blocks after the definition 340 | 341 | // there may be an example 342 | try { 343 | const hasExample = blocks.length > 0 && 344 | blocks[0].children[0]?.tagName === "Q"; 345 | if (hasExample) { 346 | def.example = blocks[0].children[0].textContent!; 347 | blocks.shift(); 348 | } 349 | } catch (e) { 350 | if (e instanceof Error) { 351 | throw new Error( 352 | `Failed parsing definition's example: ${e.message}. ` + 353 | JSON.stringify(def), 354 | ); 355 | } 356 | throw e; 357 | } 358 | 359 | // there may be synonyms 360 | const hasSynonyms = blocks.length > 0 && 361 | blocks[0].textContent === "Synonyms:"; 362 | if (hasSynonyms) { 363 | blocks.shift(); 364 | def.synonyms = {}; 365 | while (blocks.length > 0) { 366 | const words = Array.from(blocks[0].children); 367 | const hasType = words[0].textContent!.includes(":"); 368 | const type = hasType 369 | ? words[0].textContent!.split(":")[0] 370 | : "common"; 371 | if (hasType) { 372 | words.shift(); 373 | } 374 | def.synonyms[type] = words.map((w) => w.textContent!.trim()); 375 | blocks.shift(); 376 | } 377 | } 378 | 379 | ret[currentPos].push(def); 380 | 381 | // definition block end 382 | } 383 | } 384 | } 385 | 386 | return ret; 387 | }); 388 | 389 | const examples = lite || noDetails 390 | ? undefined 391 | : await page.evaluate((from) => { 392 | const egBlocks = Array.from( 393 | document.querySelectorAll( 394 | `c-wiz[role='complementary'] > div > c-wiz > div > div > div > div:nth-child(2) > div > div div[lang=${from}]`, 395 | ), 396 | ); 397 | return egBlocks.map((el) => el.textContent!) as IExamples; 398 | }, from); 399 | 400 | const translations = lite || noDetails 401 | ? undefined 402 | : await page.evaluate(() => { 403 | const ret: ITranslations = {}; 404 | Array.from( 405 | document.querySelectorAll("table > tbody"), 406 | ).forEach((tbody) => { 407 | const [tr0, ...trs] = Array.from(tbody.children); 408 | const [th0, ...tds] = Array.from(tr0.children); 409 | const PoS = th0.textContent!.toLowerCase(); 410 | if (PoS === "") return; 411 | trs.push({ children: tds } as unknown as Element); 412 | ret[PoS] = trs.map(({ children }) => { 413 | const [trans, reverseTranses, freq] = Array.from(children); 414 | return { 415 | translation: trans.textContent?.trim()!, 416 | reversedTranslations: Array.from( 417 | reverseTranses.children[0].children, 418 | ) 419 | .map((c) => c.textContent!.replace(", ", "").split(", ")) 420 | .flat(), 421 | frequency: 422 | freq.firstElementChild?.firstElementChild?.firstElementChild 423 | ?.firstElementChild 424 | ?.getAttribute("aria-label") 425 | ?.toLowerCase() ?? 426 | freq.firstElementChild?.firstElementChild?.firstElementChild 427 | ?.firstElementChild?.firstElementChild 428 | ?.getAttribute("aria-label") 429 | ?.toLowerCase()!, 430 | }; 431 | }); 432 | }); 433 | return ret; 434 | }); 435 | 436 | return { 437 | result, 438 | // fromISO, 439 | ...((fromDidYouMean || fromSuggestions || fromPronunciation) && { 440 | from: { 441 | ...(fromDidYouMean && { didYouMean: fromDidYouMean }), 442 | ...(fromSuggestions && { suggestions: fromSuggestions }), 443 | ...(fromPronunciation && { pronunciation: fromPronunciation }), 444 | }, 445 | }), 446 | ...(pronunciation && { pronunciation }), 447 | examples, 448 | definitions, 449 | translations, 450 | }; 451 | }; 452 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@std/cli@^1.0.16": "1.0.16", 5 | "jsr:@std/encoding@^1.0.9": "1.0.9", 6 | "jsr:@std/fmt@^1.0.6": "1.0.6", 7 | "jsr:@std/html@^1.0.3": "1.0.3", 8 | "jsr:@std/http@^1.0.14": "1.0.14", 9 | "jsr:@std/media-types@^1.1.0": "1.1.0", 10 | "jsr:@std/net@^1.0.4": "1.0.4", 11 | "jsr:@std/path@^1.0.8": "1.0.8", 12 | "jsr:@std/streams@^1.0.9": "1.0.9", 13 | "npm:puppeteer-extra-plugin-stealth@2.11.2": "2.11.2_puppeteer-extra@3.3.6__puppeteer@24.6.1___devtools-protocol@0.0.1425554_puppeteer@24.6.1__devtools-protocol@0.0.1425554", 14 | "npm:puppeteer-extra@3.3.6": "3.3.6_puppeteer@24.6.1__devtools-protocol@0.0.1425554", 15 | "npm:puppeteer-real-browser@^1.4.2": "1.4.2_puppeteer@24.6.1__devtools-protocol@0.0.1425554", 16 | "npm:puppeteer@24.6.1": "24.6.1_devtools-protocol@0.0.1425554" 17 | }, 18 | "jsr": { 19 | "@std/cli@1.0.16": { 20 | "integrity": "02df293099c35b9e97d8ca05f57f54bd1ee08134f25d19a4756b3924695f4b00" 21 | }, 22 | "@std/encoding@1.0.9": { 23 | "integrity": "025b8f18eb1749bc30d1353bf48b77d1eb5e35610220fa226f5a046b9240c5d7" 24 | }, 25 | "@std/fmt@1.0.6": { 26 | "integrity": "a2c56a69a2369876ddb3ad6a500bb6501b5bad47bb3ea16bfb0c18974d2661fc" 27 | }, 28 | "@std/html@1.0.3": { 29 | "integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988" 30 | }, 31 | "@std/http@1.0.14": { 32 | "integrity": "bfc4329d975dff2abd8170e83e37deb454fbc678fec3fdd2ef5b03d92768a4ef", 33 | "dependencies": [ 34 | "jsr:@std/cli", 35 | "jsr:@std/encoding", 36 | "jsr:@std/fmt", 37 | "jsr:@std/html", 38 | "jsr:@std/media-types", 39 | "jsr:@std/net", 40 | "jsr:@std/path", 41 | "jsr:@std/streams" 42 | ] 43 | }, 44 | "@std/media-types@1.1.0": { 45 | "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" 46 | }, 47 | "@std/net@1.0.4": { 48 | "integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852" 49 | }, 50 | "@std/path@1.0.8": { 51 | "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" 52 | }, 53 | "@std/streams@1.0.9": { 54 | "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035" 55 | } 56 | }, 57 | "npm": { 58 | "@babel/code-frame@7.26.2": { 59 | "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", 60 | "dependencies": [ 61 | "@babel/helper-validator-identifier", 62 | "js-tokens", 63 | "picocolors" 64 | ] 65 | }, 66 | "@babel/helper-validator-identifier@7.25.9": { 67 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" 68 | }, 69 | "@puppeteer/browsers@2.10.0": { 70 | "integrity": "sha512-HdHF4rny4JCvIcm7V1dpvpctIGqM3/Me255CB44vW7hDG1zYMmcBMjpNqZEDxdCfXGLkx5kP0+Jz5DUS+ukqtA==", 71 | "dependencies": [ 72 | "debug@4.4.0", 73 | "extract-zip", 74 | "progress", 75 | "proxy-agent", 76 | "semver", 77 | "tar-fs", 78 | "yargs" 79 | ] 80 | }, 81 | "@puppeteer/browsers@2.6.1": { 82 | "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", 83 | "dependencies": [ 84 | "debug@4.4.0", 85 | "extract-zip", 86 | "progress", 87 | "proxy-agent", 88 | "semver", 89 | "tar-fs", 90 | "unbzip2-stream", 91 | "yargs" 92 | ] 93 | }, 94 | "@tootallnate/quickjs-emscripten@0.23.0": { 95 | "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" 96 | }, 97 | "@types/bezier-js@4.1.3": { 98 | "integrity": "sha512-FNVVCu5mx/rJCWBxLTcL7oOajmGtWtBTDjq6DSUWUI12GeePivrZZXz+UgE0D6VYsLEjvExRO03z4hVtu3pTEQ==" 99 | }, 100 | "@types/debug@4.1.12": { 101 | "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", 102 | "dependencies": [ 103 | "@types/ms" 104 | ] 105 | }, 106 | "@types/ms@2.1.0": { 107 | "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" 108 | }, 109 | "@types/node@22.12.0": { 110 | "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", 111 | "dependencies": [ 112 | "undici-types" 113 | ] 114 | }, 115 | "@types/yauzl@2.10.3": { 116 | "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", 117 | "dependencies": [ 118 | "@types/node" 119 | ] 120 | }, 121 | "agent-base@7.1.3": { 122 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==" 123 | }, 124 | "ansi-regex@5.0.1": { 125 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 126 | }, 127 | "ansi-styles@4.3.0": { 128 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 129 | "dependencies": [ 130 | "color-convert" 131 | ] 132 | }, 133 | "argparse@2.0.1": { 134 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 135 | }, 136 | "arr-union@3.1.0": { 137 | "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" 138 | }, 139 | "ast-types@0.13.4": { 140 | "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", 141 | "dependencies": [ 142 | "tslib" 143 | ] 144 | }, 145 | "b4a@1.6.7": { 146 | "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" 147 | }, 148 | "balanced-match@1.0.2": { 149 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 150 | }, 151 | "bare-events@2.5.4": { 152 | "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==" 153 | }, 154 | "bare-fs@4.1.2_bare-events@2.5.4": { 155 | "integrity": "sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==", 156 | "dependencies": [ 157 | "bare-events", 158 | "bare-path", 159 | "bare-stream" 160 | ] 161 | }, 162 | "bare-os@3.6.1": { 163 | "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==" 164 | }, 165 | "bare-path@3.0.0": { 166 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", 167 | "dependencies": [ 168 | "bare-os" 169 | ] 170 | }, 171 | "bare-stream@2.6.5_bare-events@2.5.4": { 172 | "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", 173 | "dependencies": [ 174 | "bare-events", 175 | "streamx" 176 | ] 177 | }, 178 | "base64-js@1.5.1": { 179 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 180 | }, 181 | "basic-ftp@5.0.5": { 182 | "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==" 183 | }, 184 | "bezier-js@6.1.4": { 185 | "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==" 186 | }, 187 | "brace-expansion@1.1.11": { 188 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 189 | "dependencies": [ 190 | "balanced-match", 191 | "concat-map" 192 | ] 193 | }, 194 | "buffer-crc32@0.2.13": { 195 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" 196 | }, 197 | "buffer@5.7.1": { 198 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 199 | "dependencies": [ 200 | "base64-js", 201 | "ieee754" 202 | ] 203 | }, 204 | "callsites@3.1.0": { 205 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" 206 | }, 207 | "chrome-launcher@1.1.2": { 208 | "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", 209 | "dependencies": [ 210 | "@types/node", 211 | "escape-string-regexp", 212 | "is-wsl", 213 | "lighthouse-logger" 214 | ] 215 | }, 216 | "chromium-bidi@0.8.0_devtools-protocol@0.0.1367902": { 217 | "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", 218 | "dependencies": [ 219 | "devtools-protocol@0.0.1367902", 220 | "mitt", 221 | "urlpattern-polyfill", 222 | "zod@3.23.8" 223 | ] 224 | }, 225 | "chromium-bidi@3.0.0_devtools-protocol@0.0.1425554": { 226 | "integrity": "sha512-ZOGRDAhBMX1uxL2Cm2TDuhImbrsEz5A/tTcVU6RpXEWaTNUNwsHW6njUXizh51Ir6iqHbKAfhA2XK33uBcLo5A==", 227 | "dependencies": [ 228 | "devtools-protocol@0.0.1425554", 229 | "mitt", 230 | "zod@3.24.2" 231 | ] 232 | }, 233 | "cliui@8.0.1": { 234 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 235 | "dependencies": [ 236 | "string-width", 237 | "strip-ansi", 238 | "wrap-ansi" 239 | ] 240 | }, 241 | "clone-deep@0.2.4": { 242 | "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", 243 | "dependencies": [ 244 | "for-own", 245 | "is-plain-object", 246 | "kind-of@3.2.2", 247 | "lazy-cache@1.0.4", 248 | "shallow-clone" 249 | ] 250 | }, 251 | "color-convert@2.0.1": { 252 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 253 | "dependencies": [ 254 | "color-name" 255 | ] 256 | }, 257 | "color-name@1.1.4": { 258 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 259 | }, 260 | "concat-map@0.0.1": { 261 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 262 | }, 263 | "cosmiconfig@9.0.0": { 264 | "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", 265 | "dependencies": [ 266 | "env-paths", 267 | "import-fresh", 268 | "js-yaml", 269 | "parse-json" 270 | ] 271 | }, 272 | "data-uri-to-buffer@6.0.2": { 273 | "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==" 274 | }, 275 | "debug@2.6.9": { 276 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 277 | "dependencies": [ 278 | "ms@2.0.0" 279 | ] 280 | }, 281 | "debug@4.4.0": { 282 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 283 | "dependencies": [ 284 | "ms@2.1.3" 285 | ] 286 | }, 287 | "deepmerge@4.3.1": { 288 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" 289 | }, 290 | "degenerator@5.0.1": { 291 | "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", 292 | "dependencies": [ 293 | "ast-types", 294 | "escodegen", 295 | "esprima" 296 | ] 297 | }, 298 | "devtools-protocol@0.0.1367902": { 299 | "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==" 300 | }, 301 | "devtools-protocol@0.0.1425554": { 302 | "integrity": "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==" 303 | }, 304 | "emoji-regex@8.0.0": { 305 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 306 | }, 307 | "end-of-stream@1.4.4": { 308 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 309 | "dependencies": [ 310 | "once" 311 | ] 312 | }, 313 | "env-paths@2.2.1": { 314 | "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" 315 | }, 316 | "error-ex@1.3.2": { 317 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 318 | "dependencies": [ 319 | "is-arrayish" 320 | ] 321 | }, 322 | "escalade@3.2.0": { 323 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" 324 | }, 325 | "escape-string-regexp@4.0.0": { 326 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" 327 | }, 328 | "escodegen@2.1.0": { 329 | "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", 330 | "dependencies": [ 331 | "esprima", 332 | "estraverse", 333 | "esutils", 334 | "source-map" 335 | ] 336 | }, 337 | "esprima@4.0.1": { 338 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 339 | }, 340 | "estraverse@5.3.0": { 341 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" 342 | }, 343 | "esutils@2.0.3": { 344 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 345 | }, 346 | "extract-zip@2.0.1": { 347 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", 348 | "dependencies": [ 349 | "@types/yauzl", 350 | "debug@4.4.0", 351 | "get-stream", 352 | "yauzl" 353 | ] 354 | }, 355 | "fast-fifo@1.3.2": { 356 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" 357 | }, 358 | "fd-slicer@1.1.0": { 359 | "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", 360 | "dependencies": [ 361 | "pend" 362 | ] 363 | }, 364 | "for-in@0.1.8": { 365 | "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==" 366 | }, 367 | "for-in@1.0.2": { 368 | "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" 369 | }, 370 | "for-own@0.1.5": { 371 | "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", 372 | "dependencies": [ 373 | "for-in@1.0.2" 374 | ] 375 | }, 376 | "fs-extra@10.1.0": { 377 | "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 378 | "dependencies": [ 379 | "graceful-fs", 380 | "jsonfile", 381 | "universalify" 382 | ] 383 | }, 384 | "fs.realpath@1.0.0": { 385 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 386 | }, 387 | "get-caller-file@2.0.5": { 388 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 389 | }, 390 | "get-stream@5.2.0": { 391 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 392 | "dependencies": [ 393 | "pump" 394 | ] 395 | }, 396 | "get-uri@6.0.4": { 397 | "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", 398 | "dependencies": [ 399 | "basic-ftp", 400 | "data-uri-to-buffer", 401 | "debug@4.4.0" 402 | ] 403 | }, 404 | "ghost-cursor@1.4.1": { 405 | "integrity": "sha512-K8A8/Co/Jbdqee694qrNsGWBG51DVK5UF2gGKEoZBDx9F1WmoD2SzUoDHWoY7O+TY84s1VrWwwfkVKxI2FoV2Q==", 406 | "dependencies": [ 407 | "@types/bezier-js", 408 | "bezier-js", 409 | "debug@4.4.0" 410 | ] 411 | }, 412 | "glob@7.2.3": { 413 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 414 | "dependencies": [ 415 | "fs.realpath", 416 | "inflight", 417 | "inherits", 418 | "minimatch", 419 | "once", 420 | "path-is-absolute" 421 | ] 422 | }, 423 | "graceful-fs@4.2.11": { 424 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" 425 | }, 426 | "http-proxy-agent@7.0.2": { 427 | "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", 428 | "dependencies": [ 429 | "agent-base", 430 | "debug@4.4.0" 431 | ] 432 | }, 433 | "https-proxy-agent@7.0.6": { 434 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 435 | "dependencies": [ 436 | "agent-base", 437 | "debug@4.4.0" 438 | ] 439 | }, 440 | "ieee754@1.2.1": { 441 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 442 | }, 443 | "import-fresh@3.3.1": { 444 | "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 445 | "dependencies": [ 446 | "parent-module", 447 | "resolve-from" 448 | ] 449 | }, 450 | "inflight@1.0.6": { 451 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 452 | "dependencies": [ 453 | "once", 454 | "wrappy" 455 | ] 456 | }, 457 | "inherits@2.0.4": { 458 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 459 | }, 460 | "ip-address@9.0.5": { 461 | "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", 462 | "dependencies": [ 463 | "jsbn", 464 | "sprintf-js" 465 | ] 466 | }, 467 | "is-arrayish@0.2.1": { 468 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" 469 | }, 470 | "is-buffer@1.1.6": { 471 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 472 | }, 473 | "is-docker@2.2.1": { 474 | "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" 475 | }, 476 | "is-extendable@0.1.1": { 477 | "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" 478 | }, 479 | "is-fullwidth-code-point@3.0.0": { 480 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 481 | }, 482 | "is-plain-object@2.0.4": { 483 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 484 | "dependencies": [ 485 | "isobject" 486 | ] 487 | }, 488 | "is-wsl@2.2.0": { 489 | "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", 490 | "dependencies": [ 491 | "is-docker" 492 | ] 493 | }, 494 | "isobject@3.0.1": { 495 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" 496 | }, 497 | "js-tokens@4.0.0": { 498 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 499 | }, 500 | "js-yaml@4.1.0": { 501 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 502 | "dependencies": [ 503 | "argparse" 504 | ] 505 | }, 506 | "jsbn@1.1.0": { 507 | "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" 508 | }, 509 | "json-parse-even-better-errors@2.3.1": { 510 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" 511 | }, 512 | "jsonfile@6.1.0": { 513 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 514 | "dependencies": [ 515 | "graceful-fs", 516 | "universalify" 517 | ] 518 | }, 519 | "kind-of@2.0.1": { 520 | "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", 521 | "dependencies": [ 522 | "is-buffer" 523 | ] 524 | }, 525 | "kind-of@3.2.2": { 526 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", 527 | "dependencies": [ 528 | "is-buffer" 529 | ] 530 | }, 531 | "lazy-cache@0.2.7": { 532 | "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==" 533 | }, 534 | "lazy-cache@1.0.4": { 535 | "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" 536 | }, 537 | "lighthouse-logger@2.0.1": { 538 | "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", 539 | "dependencies": [ 540 | "debug@2.6.9", 541 | "marky" 542 | ] 543 | }, 544 | "lines-and-columns@1.2.4": { 545 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" 546 | }, 547 | "lru-cache@7.18.3": { 548 | "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" 549 | }, 550 | "marky@1.3.0": { 551 | "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==" 552 | }, 553 | "merge-deep@3.0.3": { 554 | "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", 555 | "dependencies": [ 556 | "arr-union", 557 | "clone-deep", 558 | "kind-of@3.2.2" 559 | ] 560 | }, 561 | "minimatch@3.1.2": { 562 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 563 | "dependencies": [ 564 | "brace-expansion" 565 | ] 566 | }, 567 | "mitt@3.0.1": { 568 | "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" 569 | }, 570 | "mixin-object@2.0.1": { 571 | "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", 572 | "dependencies": [ 573 | "for-in@0.1.8", 574 | "is-extendable" 575 | ] 576 | }, 577 | "ms@2.0.0": { 578 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 579 | }, 580 | "ms@2.1.3": { 581 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 582 | }, 583 | "nan@2.22.2": { 584 | "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==" 585 | }, 586 | "netmask@2.0.2": { 587 | "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" 588 | }, 589 | "once@1.4.0": { 590 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 591 | "dependencies": [ 592 | "wrappy" 593 | ] 594 | }, 595 | "pac-proxy-agent@7.2.0": { 596 | "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", 597 | "dependencies": [ 598 | "@tootallnate/quickjs-emscripten", 599 | "agent-base", 600 | "debug@4.4.0", 601 | "get-uri", 602 | "http-proxy-agent", 603 | "https-proxy-agent", 604 | "pac-resolver", 605 | "socks-proxy-agent" 606 | ] 607 | }, 608 | "pac-resolver@7.0.1": { 609 | "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", 610 | "dependencies": [ 611 | "degenerator", 612 | "netmask" 613 | ] 614 | }, 615 | "parent-module@1.0.1": { 616 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 617 | "dependencies": [ 618 | "callsites" 619 | ] 620 | }, 621 | "parse-json@5.2.0": { 622 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 623 | "dependencies": [ 624 | "@babel/code-frame", 625 | "error-ex", 626 | "json-parse-even-better-errors", 627 | "lines-and-columns" 628 | ] 629 | }, 630 | "path-is-absolute@1.0.1": { 631 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 632 | }, 633 | "pend@1.2.0": { 634 | "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" 635 | }, 636 | "picocolors@1.1.1": { 637 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 638 | }, 639 | "progress@2.0.3": { 640 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 641 | }, 642 | "proxy-agent@6.5.0": { 643 | "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", 644 | "dependencies": [ 645 | "agent-base", 646 | "debug@4.4.0", 647 | "http-proxy-agent", 648 | "https-proxy-agent", 649 | "lru-cache", 650 | "pac-proxy-agent", 651 | "proxy-from-env", 652 | "socks-proxy-agent" 653 | ] 654 | }, 655 | "proxy-from-env@1.1.0": { 656 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 657 | }, 658 | "pump@3.0.2": { 659 | "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", 660 | "dependencies": [ 661 | "end-of-stream", 662 | "once" 663 | ] 664 | }, 665 | "puppeteer-core@24.6.1_devtools-protocol@0.0.1425554": { 666 | "integrity": "sha512-sMCxsY+OPWO2fecBrhIeCeJbWWXJ6UaN997sTid6whY0YT9XM0RnxEwLeUibluIS5/fRmuxe1efjb5RMBsky7g==", 667 | "dependencies": [ 668 | "@puppeteer/browsers@2.10.0", 669 | "chromium-bidi@3.0.0_devtools-protocol@0.0.1425554", 670 | "debug@4.4.0", 671 | "devtools-protocol@0.0.1425554", 672 | "typed-query-selector", 673 | "ws" 674 | ] 675 | }, 676 | "puppeteer-extra-plugin-stealth@2.11.2_puppeteer-extra@3.3.6__puppeteer@24.6.1___devtools-protocol@0.0.1425554_puppeteer@24.6.1__devtools-protocol@0.0.1425554": { 677 | "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", 678 | "dependencies": [ 679 | "debug@4.4.0", 680 | "puppeteer-extra", 681 | "puppeteer-extra-plugin", 682 | "puppeteer-extra-plugin-user-preferences" 683 | ] 684 | }, 685 | "puppeteer-extra-plugin-user-data-dir@2.4.1_puppeteer-extra@3.3.6__puppeteer@24.6.1___devtools-protocol@0.0.1425554_puppeteer@24.6.1__devtools-protocol@0.0.1425554": { 686 | "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", 687 | "dependencies": [ 688 | "debug@4.4.0", 689 | "fs-extra", 690 | "puppeteer-extra", 691 | "puppeteer-extra-plugin", 692 | "rimraf" 693 | ] 694 | }, 695 | "puppeteer-extra-plugin-user-preferences@2.4.1_puppeteer-extra@3.3.6__puppeteer@24.6.1___devtools-protocol@0.0.1425554_puppeteer@24.6.1__devtools-protocol@0.0.1425554": { 696 | "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", 697 | "dependencies": [ 698 | "debug@4.4.0", 699 | "deepmerge", 700 | "puppeteer-extra", 701 | "puppeteer-extra-plugin", 702 | "puppeteer-extra-plugin-user-data-dir" 703 | ] 704 | }, 705 | "puppeteer-extra-plugin@3.2.3_puppeteer-extra@3.3.6__puppeteer@24.6.1___devtools-protocol@0.0.1425554_puppeteer@24.6.1__devtools-protocol@0.0.1425554": { 706 | "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", 707 | "dependencies": [ 708 | "@types/debug", 709 | "debug@4.4.0", 710 | "merge-deep", 711 | "puppeteer-extra" 712 | ] 713 | }, 714 | "puppeteer-extra@3.3.6_puppeteer@24.6.1__devtools-protocol@0.0.1425554": { 715 | "integrity": "sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==", 716 | "dependencies": [ 717 | "@types/debug", 718 | "debug@4.4.0", 719 | "deepmerge", 720 | "puppeteer" 721 | ] 722 | }, 723 | "puppeteer-real-browser@1.4.2_puppeteer@24.6.1__devtools-protocol@0.0.1425554": { 724 | "integrity": "sha512-HgL8HWy2VIViJSACK/FcklwqRDBRvMjVjIR4f7Pe8VNn5kPxBe9HTVEu0ckx9mE/61BgiUADJZn25nPQn4FOsw==", 725 | "dependencies": [ 726 | "chrome-launcher", 727 | "ghost-cursor", 728 | "puppeteer-extra", 729 | "rebrowser-puppeteer-core", 730 | "tree-kill", 731 | "xvfb" 732 | ] 733 | }, 734 | "puppeteer@24.6.1_devtools-protocol@0.0.1425554": { 735 | "integrity": "sha512-/4ocGfu8LNvDbWUqJZV2VmwEWpbOdJa69y2Jivd213tV0ekAtUh/bgT1hhW63SDN/CtrEucOPwoomZ+9M+eBEg==", 736 | "dependencies": [ 737 | "@puppeteer/browsers@2.10.0", 738 | "chromium-bidi@3.0.0_devtools-protocol@0.0.1425554", 739 | "cosmiconfig", 740 | "devtools-protocol@0.0.1425554", 741 | "puppeteer-core", 742 | "typed-query-selector" 743 | ] 744 | }, 745 | "rebrowser-puppeteer-core@23.10.3_devtools-protocol@0.0.1367902": { 746 | "integrity": "sha512-oWwuFg3XoZUkAt6Te4zTU6sQeS39I9tctjdSEiDPa76MF47R0IfLX8VQhyRwwzMySqD5L1wambYcWyEAN0b9AA==", 747 | "dependencies": [ 748 | "@puppeteer/browsers@2.6.1", 749 | "chromium-bidi@0.8.0_devtools-protocol@0.0.1367902", 750 | "debug@4.4.0", 751 | "devtools-protocol@0.0.1367902", 752 | "typed-query-selector", 753 | "ws" 754 | ] 755 | }, 756 | "require-directory@2.1.1": { 757 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" 758 | }, 759 | "resolve-from@4.0.0": { 760 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" 761 | }, 762 | "rimraf@3.0.2": { 763 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 764 | "dependencies": [ 765 | "glob" 766 | ] 767 | }, 768 | "semver@7.7.1": { 769 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" 770 | }, 771 | "shallow-clone@0.1.2": { 772 | "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", 773 | "dependencies": [ 774 | "is-extendable", 775 | "kind-of@2.0.1", 776 | "lazy-cache@0.2.7", 777 | "mixin-object" 778 | ] 779 | }, 780 | "sleep@6.1.0": { 781 | "integrity": "sha512-Z1x4JjJxsru75Tqn8F4tnOFeEu3HjtITTsumYUiuz54sGKdISgLCek9AUlXlVVrkhltRFhNUsJDJE76SFHTDIQ==", 782 | "dependencies": [ 783 | "nan" 784 | ] 785 | }, 786 | "smart-buffer@4.2.0": { 787 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" 788 | }, 789 | "socks-proxy-agent@8.0.5": { 790 | "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", 791 | "dependencies": [ 792 | "agent-base", 793 | "debug@4.4.0", 794 | "socks" 795 | ] 796 | }, 797 | "socks@2.8.4": { 798 | "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", 799 | "dependencies": [ 800 | "ip-address", 801 | "smart-buffer" 802 | ] 803 | }, 804 | "source-map@0.6.1": { 805 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 806 | }, 807 | "sprintf-js@1.1.3": { 808 | "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" 809 | }, 810 | "streamx@2.22.0": { 811 | "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", 812 | "dependencies": [ 813 | "bare-events", 814 | "fast-fifo", 815 | "text-decoder" 816 | ] 817 | }, 818 | "string-width@4.2.3": { 819 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 820 | "dependencies": [ 821 | "emoji-regex", 822 | "is-fullwidth-code-point", 823 | "strip-ansi" 824 | ] 825 | }, 826 | "strip-ansi@6.0.1": { 827 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 828 | "dependencies": [ 829 | "ansi-regex" 830 | ] 831 | }, 832 | "tar-fs@3.0.8": { 833 | "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", 834 | "dependencies": [ 835 | "bare-fs", 836 | "bare-path", 837 | "pump", 838 | "tar-stream" 839 | ] 840 | }, 841 | "tar-stream@3.1.7": { 842 | "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", 843 | "dependencies": [ 844 | "b4a", 845 | "fast-fifo", 846 | "streamx" 847 | ] 848 | }, 849 | "text-decoder@1.2.3": { 850 | "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", 851 | "dependencies": [ 852 | "b4a" 853 | ] 854 | }, 855 | "through@2.3.8": { 856 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" 857 | }, 858 | "tree-kill@1.2.2": { 859 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" 860 | }, 861 | "tslib@2.8.1": { 862 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" 863 | }, 864 | "typed-query-selector@2.12.0": { 865 | "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" 866 | }, 867 | "unbzip2-stream@1.4.3": { 868 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", 869 | "dependencies": [ 870 | "buffer", 871 | "through" 872 | ] 873 | }, 874 | "undici-types@6.20.0": { 875 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" 876 | }, 877 | "universalify@2.0.1": { 878 | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" 879 | }, 880 | "urlpattern-polyfill@10.0.0": { 881 | "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" 882 | }, 883 | "wrap-ansi@7.0.0": { 884 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 885 | "dependencies": [ 886 | "ansi-styles", 887 | "string-width", 888 | "strip-ansi" 889 | ] 890 | }, 891 | "wrappy@1.0.2": { 892 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 893 | }, 894 | "ws@8.18.1": { 895 | "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==" 896 | }, 897 | "xvfb@0.4.0": { 898 | "integrity": "sha512-g55AbjcBL4Bztfn7kiUrR0ne8mMUsFODDJ+HFGf5OuHJqKKccpExX2Qgn7VF2eImw1eoh6+riXHser1J4agrFA==", 899 | "dependencies": [ 900 | "sleep" 901 | ] 902 | }, 903 | "y18n@5.0.8": { 904 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 905 | }, 906 | "yargs-parser@21.1.1": { 907 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" 908 | }, 909 | "yargs@17.7.2": { 910 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 911 | "dependencies": [ 912 | "cliui", 913 | "escalade", 914 | "get-caller-file", 915 | "require-directory", 916 | "string-width", 917 | "y18n", 918 | "yargs-parser" 919 | ] 920 | }, 921 | "yauzl@2.10.0": { 922 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", 923 | "dependencies": [ 924 | "buffer-crc32", 925 | "fd-slicer" 926 | ] 927 | }, 928 | "zod@3.23.8": { 929 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==" 930 | }, 931 | "zod@3.24.2": { 932 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==" 933 | } 934 | }, 935 | "workspace": { 936 | "dependencies": [ 937 | "jsr:@std/http@^1.0.14", 938 | "jsr:@std/path@^1.0.8", 939 | "npm:puppeteer-extra-plugin-stealth@2.11.2", 940 | "npm:puppeteer-extra@3.3.6", 941 | "npm:puppeteer-real-browser@^1.4.2", 942 | "npm:puppeteer@24.6.1" 943 | ] 944 | } 945 | } 946 | --------------------------------------------------------------------------------