fix(route/picnob): use puppeteer-real-browser to pass cf check (#20478)

This commit is contained in:
Stephen Zhou
2025-11-14 15:32:09 +08:00
committed by GitHub
parent a07565d356
commit 400fb1fa49
6 changed files with 346 additions and 55 deletions

View File

@@ -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; \

View File

@@ -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

View File

@@ -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

View File

@@ -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<string> {
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<string> {
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: `<img src="${$item.find('.preview_w img').attr('data-src')}" /><br />${sum.replaceAll('\n', '<br>')}`,
title,
description: `<img src="${image.attr('data-src')}" /><br />${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 `<video src="${$('.video_img a').attr('href')}" poster="${$('.video_img img').attr('data-src')}"></video><br />${$('.sum_full').text()}`;
} else {
let description = '';
for (const slide of $('.swiper-slide').toArray()) {
const $slide = $(slide);
description += `<img src="${$slide.find('.pic img').attr('data-src')}" /><br />`;
// 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 `<video src="${$('.video_img a').attr('href')}" poster="${$('.video_img img').attr('data-src')}"></video><br />${$('.sum_full').text()}`;
} else {
let description = '';
for (const slide of $('.swiper-slide').toArray()) {
const $slide = $(slide);
description += `<img src="${$slide.find('.pic img').attr('data-src')}" /><br />`;
}
description += $('.sum_full').text();
return description;
}
});
return {
title: `${$('h1.fullname').text()} (@${id}) ${type === 'tagged' ? 'tagged' : 'public'} posts - Picnob`,

View File

@@ -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"
],

203
pnpm-lock.yaml generated
View File

@@ -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: {}