diff --git a/Dockerfile b/Dockerfile index b694b0e383..4d7c07908d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -148,7 +148,7 @@ RUN \ ; \ else \ apt-get install -yq --no-install-recommends \ - chromium \ + chromium xvfb \ && \ echo "CHROMIUM_EXECUTABLE_PATH=$(which chromium)" | tee /app/.env ; \ fi; \ diff --git a/docker-compose.yml b/docker-compose.yml index d26a83bd80..33f9172b1a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,14 +6,15 @@ services: image: diygod/rsshub # or ghcr.io/diygod/rsshub restart: always ports: - - "1200:1200" + - '1200:1200' environment: NODE_ENV: production CACHE_TYPE: redis - REDIS_URL: "redis://redis:6379/" - PUPPETEER_WS_ENDPOINT: "ws://browserless:3000" # marked + REDIS_URL: 'redis://redis:6379/' + PUPPETEER_WS_ENDPOINT: 'ws://browserless:3000' # marked + PUPPETEER_REAL_BROWSER_SERVICE: 'http://real-browser:3000' # marked healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:1200/healthz"] + test: ['CMD', 'curl', '-f', 'http://localhost:1200/healthz'] interval: 30s timeout: 10s retries: 3 @@ -21,6 +22,17 @@ services: - redis - browserless # marked + real-browser: + image: ghcr.io/hyoban/puppeteer-real-browser-hono + restart: always + ports: + - '3001:3000' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000'] + interval: 30s + timeout: 10s + retries: 3 + browserless: # marked image: browserless/chrome # marked restart: always # marked @@ -29,7 +41,7 @@ services: hard: 0 # marked soft: 0 # marked healthcheck: # marked - test: ["CMD", "curl", "-f", "http://localhost:3000/pressure"] # marked + test: ['CMD', 'curl', '-f', 'http://localhost:3000/pressure'] # marked interval: 30s # marked timeout: 10s # marked retries: 3 # marked @@ -40,7 +52,7 @@ services: volumes: - redis-data:/data healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: ['CMD', 'redis-cli', 'ping'] interval: 30s timeout: 10s retries: 5 diff --git a/lib/config.ts b/lib/config.ts index bf043fca54..1fe456abe6 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -9,6 +9,7 @@ export type Config = { enableCluster?: string; isPackage: boolean; nodeName?: string; + puppeteerRealBrowserService?: string; puppeteerWSEndpoint?: string; chromiumExecutablePath?: string; // network @@ -480,6 +481,7 @@ const calculateValue = () => { enableCluster: toBoolean(envs.ENABLE_CLUSTER, false), isPackage: !!envs.IS_PACKAGE, nodeName: envs.NODE_NAME, + puppeteerRealBrowserService: envs.PUPPETEER_REAL_BROWSER_SERVICE, puppeteerWSEndpoint: envs.PUPPETEER_WS_ENDPOINT, chromiumExecutablePath: envs.CHROMIUM_EXECUTABLE_PATH, // network diff --git a/lib/routes/picnob/user.ts b/lib/routes/picnob/user.ts index c4b26d9a08..5fa855e8f6 100644 --- a/lib/routes/picnob/user.ts +++ b/lib/routes/picnob/user.ts @@ -1,11 +1,50 @@ +import { config } from '@/config'; import { Route, ViewType } from '@/types'; - -import ofetch from '@/utils/ofetch'; -import { load } from 'cheerio'; -import { parseRelativeDate } from '@/utils/parse-date'; -import { puppeteerGet } from './utils'; -import sanitizeHtml from 'sanitize-html'; import cache from '@/utils/cache'; +import { parseRelativeDate } from '@/utils/parse-date'; +import { load } from 'cheerio'; +import { connect, Options } from 'puppeteer-real-browser'; + +const realBrowserOption: Options = { + args: ['--start-maximized'], + turnstile: true, + headless: false, + // disableXvfb: true, + // ignoreAllFlags:true, + customConfig: { + chromePath: config.chromiumExecutablePath, + }, + connectOption: { + defaultViewport: null, + }, + plugins: [], +}; + +async function getPageWithPuppeteer(url: string, selector: string): Promise { + if (config.puppeteerRealBrowserService) { + const res = await fetch(`${config.puppeteerRealBrowserService}?url=${encodeURIComponent(url)}&selector=${encodeURIComponent(selector)}`); + const json = await res.json(); + return (json.data.at(0) || '') as string; + } else { + const { page, browser } = await connect(realBrowserOption); + await page.goto(url, { timeout: 50000 }); + let verify: boolean | null = null; + const startDate = Date.now(); + while (!verify && Date.now() - startDate < 50000) { + // eslint-disable-next-line no-await-in-loop, no-restricted-syntax + verify = await page.evaluate((sel) => (document.querySelector(sel) ? true : null), selector).catch(() => null); + // eslint-disable-next-line no-await-in-loop + await new Promise((r) => setTimeout(r, 1000)); + } + const html = await page.content(); + await browser.close(); + return html; + } +} + +function getProfilePage(profileUrl: string): Promise { + return getPageWithPuppeteer(profileUrl, '.post_box'); +} export const route: Route = { path: '/user/:id/:type?', @@ -34,76 +73,109 @@ export const route: Route = { }, ], name: 'User Profile - Picnob', - maintainers: ['TonyRL', 'micheal-death', 'AiraNadih', 'DIYgod'], + maintainers: ['TonyRL', 'micheal-death', 'AiraNadih', 'DIYgod', 'hyoban'], handler, view: ViewType.Pictures, }; async function handler(ctx) { + if (!config.puppeteerRealBrowserService && !config.chromiumExecutablePath) { + throw new Error('PUPPETEER_REAL_BROWSER_SERVICE or CHROMIUM_EXECUTABLE_PATH is required to use this route.'); + } + // NOTE: 'picnob' is still available, but all requests to 'picnob' will be redirected to 'pixnoy' eventually const baseUrl = 'https://www.pixnoy.com'; const id = ctx.req.param('id'); const type = ctx.req.param('type') ?? 'profile'; const profileUrl = `${baseUrl}/profile/${id}/${type === 'tagged' ? 'tagged/' : ''}`; - // TODO: can't bypass cloudflare 403 error without puppeteer - let html; - let usePuppeteer = false; - try { - const data = await ofetch(profileUrl); - html = data; - } catch { - html = await puppeteerGet(profileUrl); - usePuppeteer = true; - } + const html = await getProfilePage(profileUrl); + const $ = load(html); const list = $('.post_box') .toArray() .map((item) => { const $item = $(item); - const sum = $item.find('.sum').text(); const coverLink = $item.find('.cover_link').attr('href'); const shortcode = coverLink?.split('/')?.[2]; + const image = $item.find('.cover .cover_link img'); + const title = image.attr('alt') || ''; return { - title: sanitizeHtml(sum.split('\n')[0], { allowedTags: [], allowedAttributes: {} }), - description: `
${sum.replaceAll('\n', '
')}`, + title, + description: `
${title}`, link: `${baseUrl}${coverLink}`, guid: shortcode, pubDate: parseRelativeDate($item.find('.time .txt').text()), }; }); - const newDescription = await Promise.all( - list.map((item) => - cache.tryGet(`picnob:user:${id}:${item.guid}`, async () => { - try { - const html = usePuppeteer - ? await puppeteerGet(item.link) - : await ofetch(item.link, { - headers: { - 'user-agent': 'PostmanRuntime/7.44.0', - }, - }); - const $ = load(html); - if ($('.video_img').length > 0) { - return `
${$('.sum_full').text()}`; - } else { - let description = ''; - for (const slide of $('.swiper-slide').toArray()) { - const $slide = $(slide); - description += `
`; + // Fetch all post details concurrently + // First, get HTML for all posts + let htmlList: string[]; + if (config.puppeteerRealBrowserService) { + // Use puppeteer service for multiple URLs + htmlList = (await Promise.all( + list.map((item) => + cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => { + const selector = '.video_img, .swiper-slide'; + const res = await fetch(`${config.puppeteerRealBrowserService}?url=${encodeURIComponent(item.link)}&selector=${encodeURIComponent(selector)}`); + const json = await res.json(); + return (json.data?.at(0) || '') as string; + }) + ) + )) as string[]; + } else { + // Use local puppeteer browser + const { browser } = await connect(realBrowserOption); + try { + htmlList = (await Promise.all( + list.map((item) => + cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => { + const page = await browser.newPage(); + try { + await page.goto(item.link, { timeout: 50000 }); + let verify: boolean | null = null; + const startDate = Date.now(); + while (!verify && Date.now() - startDate < 50000) { + // eslint-disable-next-line no-await-in-loop, no-restricted-syntax + verify = await page.evaluate(() => (document.querySelector('.video_img') || document.querySelector('.swiper-slide') ? true : null)).catch(() => null); + // eslint-disable-next-line no-await-in-loop + await new Promise((r) => setTimeout(r, 1000)); + } + return await page.content(); + } catch { + return ''; + } finally { + await page.close(); } - description += $('.sum_full').text(); - return description; - } - } catch { - return ''; - } - }) - ) - ); + }) + ) + )) as string[]; + } finally { + await browser.close(); + } + } + + // Process HTML to generate descriptions + const newDescription = htmlList.map((html) => { + if (!html) { + return ''; + } + const $ = load(html); + if ($('.video_img').length > 0) { + return `
${$('.sum_full').text()}`; + } else { + let description = ''; + for (const slide of $('.swiper-slide').toArray()) { + const $slide = $(slide); + description += `
`; + } + description += $('.sum_full').text(); + return description; + } + }); return { title: `${$('h1.fullname').text()} (@${id}) ${type === 'tagged' ? 'tagged' : 'public'} posts - Picnob`, diff --git a/package.json b/package.json index 4374384ed9..3c235c788c 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "p-map": "7.0.4", "pac-proxy-agent": "7.2.0", "proxy-chain": "2.5.9", + "puppeteer-real-browser": "1.4.4", "query-string": "9.3.1", "rate-limiter-flexible": "8.2.0", "re2js": "1.2.0", @@ -213,6 +214,7 @@ "puppeteer", "rebrowser-puppeteer", "rolldown", + "sleep", "utf-8-validate", "vue-demi" ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f119758ba3..52f89a25ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,6 +204,9 @@ importers: proxy-chain: specifier: 2.5.9 version: 2.5.9 + puppeteer-real-browser: + specifier: 1.4.4 + version: 1.4.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) query-string: specifier: 9.3.1 version: 9.3.1 @@ -2025,6 +2028,11 @@ packages: engines: {node: '>=18'} hasBin: true + '@puppeteer/browsers@2.6.1': + resolution: {integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==} + engines: {node: '>=18'} + hasBin: true + '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} @@ -2367,6 +2375,9 @@ packages: '@types/babel__preset-env@7.10.0': resolution: {integrity: sha512-LS8hRb/8TQir2f8W9/s5enDtrRS2F/6fsdkVw5ePHp6Q8SrSJHOGtWnP93ryaYMmg2du03vOsiGrl5mllz4uDA==} + '@types/bezier-js@4.1.3': + resolution: {integrity: sha512-FNVVCu5mx/rJCWBxLTcL7oOajmGtWtBTDjq6DSUWUI12GeePivrZZXz+UgE0D6VYsLEjvExRO03z4hVtu3pTEQ==} + '@types/bluebird@3.5.42': resolution: {integrity: sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==} @@ -2812,6 +2823,9 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bezier-js@6.1.4: + resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==} + bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -2861,6 +2875,9 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -2960,6 +2977,16 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + chrome-launcher@1.2.1: + resolution: {integrity: sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-bidi@0.8.0: + resolution: {integrity: sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==} + peerDependencies: + devtools-protocol: '*' + chromium-bidi@5.1.0: resolution: {integrity: sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==} peerDependencies: @@ -3245,6 +3272,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + devtools-protocol@0.0.1367902: + resolution: {integrity: sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==} + devtools-protocol@0.0.1439962: resolution: {integrity: sha512-jJF48UdryzKiWhJ1bLKr7BFWUQCEIT5uCNbDLqkQJBtkFxYzILJH44WN0PDKMIlGDN7Utb8vyUY85C3w4R/t2g==} @@ -3821,6 +3851,9 @@ packages: getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + ghost-cursor@1.4.1: + resolution: {integrity: sha512-K8A8/Co/Jbdqee694qrNsGWBG51DVK5UF2gGKEoZBDx9F1WmoD2SzUoDHWoY7O+TY84s1VrWwwfkVKxI2FoV2Q==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4329,6 +4362,9 @@ packages: libqp@2.1.1: resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==} + lighthouse-logger@2.0.2: + resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4468,6 +4504,9 @@ packages: markdown-table@2.0.0: resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -4662,6 +4701,9 @@ packages: nan@1.8.4: resolution: {integrity: sha512-609zQ1h3ApgH/94qmbbEklSrjcYYXCHnsWk4MAojq4OUk3tidhDYhPaMasMFKsZPZ96r4eQA1hbR2W4H7/77XA==} + nan@2.23.1: + resolution: {integrity: sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==} + nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} @@ -5064,6 +5106,24 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + puppeteer-extra@3.3.6: + resolution: {integrity: sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==} + engines: {node: '>=8'} + peerDependencies: + '@types/puppeteer': '*' + puppeteer: '*' + puppeteer-core: '*' + peerDependenciesMeta: + '@types/puppeteer': + optional: true + puppeteer: + optional: true + puppeteer-core: + optional: true + + puppeteer-real-browser@1.4.4: + resolution: {integrity: sha512-1CYGlL1Y0SdxP55byi9WQ8dtLkyIYBmRpGr+D+cB6uZDrW17+ZxWMnc7CDZfNuNuJaF15DWW5zvChS/ikymdmg==} + qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -5117,6 +5177,10 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + rebrowser-puppeteer-core@23.10.3: + resolution: {integrity: sha512-oWwuFg3XoZUkAt6Te4zTU6sQeS39I9tctjdSEiDPa76MF47R0IfLX8VQhyRwwzMySqD5L1wambYcWyEAN0b9AA==} + engines: {node: '>=18'} + rebrowser-puppeteer-core@24.8.1: resolution: {integrity: sha512-CifPf47KTG0jajTyn/dWpvf3kpW5W1QesURCWaUGzNTN8Kkki2IBUKJaMS+R+eNKhfv1JcsCZ5glYI0RAyJPTg==} engines: {node: '>=18'} @@ -5340,6 +5404,10 @@ packages: simplecc-wasm@1.1.0: resolution: {integrity: sha512-0nmH9WrzxpI8GOZkltH0NKAlcPe42rXek01noMSdelCmj8RYSL06SDDG/YWhvVTbMcuN1fq5imOeXpUJmhbcPQ==} + sleep@6.1.0: + resolution: {integrity: sha512-Z1x4JjJxsru75Tqn8F4tnOFeEu3HjtITTsumYUiuz54sGKdISgLCek9AUlXlVVrkhltRFhNUsJDJE76SFHTDIQ==} + engines: {node: '>=0.8.0'} + slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} @@ -5544,6 +5612,9 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -5787,6 +5858,9 @@ packages: ultron@1.0.2: resolution: {integrity: sha512-QMpnpVtYaWEeY+MwKDN/UdKlE/LsFZXM5lO1u7GaZzNgmIbGixHEmVMIKT+vqYOALu3m5GYQy9kz4Xu4IVn7Ow==} + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + unconfig-core@7.4.1: resolution: {integrity: sha512-Bp/bPZjV2Vl/fofoA2OYLSnw1Z0MOhCX7zHnVCYrazpfZvseBbGhwcNQMxsg185Mqh7VZQqK3C8hFG/Dyng+yA==} @@ -5871,6 +5945,9 @@ packages: url-template@2.0.8: resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} + urlpattern-polyfill@10.0.0: + resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} + utf-8-validate@1.1.0: resolution: {integrity: sha512-Qsgu1u2akdyOneurUEVf/tXhkqBQMoE3x0GY5+P7ayPHvVzTqOkkgBhtl26wm6ap10Amhwy0jIhPwMGywx76QQ==} @@ -6102,6 +6179,9 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + xvfb@0.4.0: + resolution: {integrity: sha512-g55AbjcBL4Bztfn7kiUrR0ne8mMUsFODDJ+HFGf5OuHJqKKccpExX2Qgn7VF2eImw1eoh6+riXHser1J4agrFA==} + xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -6163,6 +6243,9 @@ packages: youtubei.js@16.0.1: resolution: {integrity: sha512-3802bCAGkBc2/G5WUTc0l/bO5mPYJbQAHL04d9hE9PnrDHoBUT8MN721Yqt4RCNncAXdHcfee9VdJy3Fhq1r5g==} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -7917,6 +8000,20 @@ snapshots: - bare-buffer - supports-color + '@puppeteer/browsers@2.6.1': + dependencies: + debug: 4.4.3 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.3 + tar-fs: 3.1.1 + unbzip2-stream: 1.4.3 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + '@quansync/fs@0.1.5': dependencies: quansync: 0.2.11 @@ -8209,6 +8306,8 @@ snapshots: '@types/babel__preset-env@7.10.0': {} + '@types/bezier-js@4.1.3': {} + '@types/bluebird@3.5.42': {} '@types/caseless@0.12.5': {} @@ -8711,6 +8810,8 @@ snapshots: dependencies: tweetnacl: 0.14.5 + bezier-js@6.1.4: {} + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 @@ -8759,6 +8860,11 @@ snapshots: buffer-equal-constant-time@1.0.1: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@6.0.3: dependencies: base64-js: 1.5.1 @@ -8885,6 +8991,22 @@ snapshots: chownr@3.0.0: {} + chrome-launcher@1.2.1: + dependencies: + '@types/node': 24.10.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 2.0.2 + transitivePeerDependencies: + - supports-color + + chromium-bidi@0.8.0(devtools-protocol@0.0.1367902): + dependencies: + devtools-protocol: 0.0.1367902 + mitt: 3.0.1 + urlpattern-polyfill: 10.0.0 + zod: 3.23.8 + chromium-bidi@5.1.0(devtools-protocol@0.0.1439962): dependencies: devtools-protocol: 0.0.1439962 @@ -9129,6 +9251,8 @@ snapshots: dependencies: dequal: 2.0.3 + devtools-protocol@0.0.1367902: {} + devtools-protocol@0.0.1439962: {} dezalgo@1.0.4: @@ -9865,6 +9989,14 @@ snapshots: dependencies: assert-plus: 1.0.0 + ghost-cursor@1.4.1: + dependencies: + '@types/bezier-js': 4.1.3 + bezier-js: 6.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -10435,6 +10567,13 @@ snapshots: libqp@2.1.1: {} + lighthouse-logger@2.0.2: + dependencies: + debug: 4.4.3 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + lines-and-columns@1.2.4: {} linkify-it@5.0.0: @@ -10579,6 +10718,8 @@ snapshots: dependencies: repeat-string: 1.6.1 + marky@1.3.0: {} + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -10837,6 +10978,9 @@ snapshots: nan@1.8.4: optional: true + nan@2.23.1: + optional: true + nano-spawn@2.0.0: {} nanoid@3.3.11: {} @@ -11272,6 +11416,31 @@ snapshots: punycode@2.3.1: {} + puppeteer-extra@3.3.6: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + deepmerge: 4.3.1 + transitivePeerDependencies: + - supports-color + + puppeteer-real-browser@1.4.4(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + chrome-launcher: 1.2.1 + ghost-cursor: 1.4.1 + puppeteer-extra: 3.3.6 + rebrowser-puppeteer-core: 23.10.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + tree-kill: 1.2.2 + xvfb: 0.4.0 + transitivePeerDependencies: + - '@types/puppeteer' + - bare-buffer + - bufferutil + - puppeteer + - puppeteer-core + - supports-color + - utf-8-validate + qs@6.14.0: dependencies: side-channel: '@nolyfill/side-channel@1.0.44' @@ -11312,6 +11481,20 @@ snapshots: real-require@0.2.0: {} + rebrowser-puppeteer-core@23.10.3(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + '@puppeteer/browsers': 2.6.1 + chromium-bidi: 0.8.0(devtools-protocol@0.0.1367902) + debug: 4.4.3 + devtools-protocol: 0.0.1367902 + typed-query-selector: 2.12.0 + ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + rebrowser-puppeteer-core@24.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: '@puppeteer/browsers': 2.10.3 @@ -11590,6 +11773,11 @@ snapshots: simplecc-wasm@1.1.0: {} + sleep@6.1.0: + dependencies: + nan: 2.23.1 + optional: true + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -11824,6 +12012,8 @@ snapshots: dependencies: real-require: 0.2.0 + through@2.3.8: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -12020,6 +12210,11 @@ snapshots: ultron@1.0.2: {} + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + unconfig-core@7.4.1: dependencies: '@quansync/fs': 0.1.5 @@ -12093,6 +12288,8 @@ snapshots: url-template@2.0.8: {} + urlpattern-polyfill@10.0.0: {} + utf-8-validate@1.1.0: dependencies: bindings: 1.2.1 @@ -12339,6 +12536,10 @@ snapshots: xtend@4.0.2: {} + xvfb@0.4.0: + optionalDependencies: + sleep: 6.1.0 + xxhash-wasm@1.1.0: {} y18n@5.0.8: {} @@ -12394,6 +12595,8 @@ snapshots: '@bufbuild/protobuf': 2.9.0 meriyah: 6.1.4 + zod@3.23.8: {} + zod@3.25.76: {} zod@4.1.11: {}