fix(route/picnob): support img not in slide

This commit is contained in:
Stephen Zhou
2025-11-14 22:10:32 +08:00
parent 3235de47f8
commit 2cb11d7c7e
+9 -92
View File
@@ -3,47 +3,11 @@ import { Route, ViewType } from '@/types';
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');
async function getPageWithRealBrowser(url: string, selector: string): Promise<string> {
const res = await fetch(`${config.puppeteerRealBrowserService}?url=${encodeURIComponent(url)}&selector=${encodeURIComponent(selector)}`);
const json = await res.json();
return (json.data?.at(0) || '') as string;
}
export const route: Route = {
@@ -79,7 +43,7 @@ export const route: Route = {
};
async function handler(ctx) {
if (!config.puppeteerRealBrowserService && !config.chromiumExecutablePath) {
if (!config.puppeteerRealBrowserService) {
throw new Error('PUPPETEER_REAL_BROWSER_SERVICE or CHROMIUM_EXECUTABLE_PATH is required to use this route.');
}
@@ -89,7 +53,7 @@ async function handler(ctx) {
const type = ctx.req.param('type') ?? 'profile';
const profileUrl = `${baseUrl}/profile/${id}/${type === 'tagged' ? 'tagged/' : ''}`;
const html = await getProfilePage(profileUrl);
const html = await getPageWithRealBrowser(profileUrl, '.post_box');
const $ = load(html);
@@ -111,54 +75,8 @@ async function handler(ctx) {
};
});
// 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();
}
})
)
)) as string[];
} finally {
await browser.close();
}
}
const htmlList = (await Promise.all(list.map((item) => cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => await getPageWithRealBrowser(item.link, '.view'))))) as string[];
// Process HTML to generate descriptions
const newDescription = htmlList.map((html) => {
if (!html) {
return '';
@@ -168,9 +86,8 @@ async function handler(ctx) {
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 />`;
for (const pic of $('.pic img').toArray()) {
description += `<img src="${$(pic).attr('data-src')}" /><br />`;
}
description += $('.sum_full').text();
return description;