fix(route/picnob): bring back support for diygod/rsshub:chromium-bundled (#20504)
* ci(docker-release): enable ZRAM to reduce memory pressure This should hopefully help reduce stuck build jobs. Note that this can't completely eliminate stuck builds since this is usually caused by synchronization issues in QEMU. Signed-off-by: Rongrong <i@rong.moe> * build(Dockerfile): Add support for puppeteer-real-browser Sets CHROMIUM_EXECUTABLE_PATH on AMD64 and installs essential dependencies. Signed-off-by: Rongrong <i@rong.moe> * fix(route/picnob): bring back support for diygod/rsshub:chromium-bundled The support for puppeteer-real-browser on diygod/rsshub:chromium-bundled was improperly implemented in400fb1fa49("fix(route/picnob): use puppeteer-real-browser to pass cf check (#20478)") and was then removed in2cb11d7c7e("fix(route/picnob): support img not in slide"). With proper support for puppeteer-real-browser added to Dockerfile, this patch brings back the support for diygod/rsshub:chromium-bundled. This partially reverts commit2cb11d7c7e("fix(route/picnob): support img not in slide"). Signed-off-by: Rongrong <i@rong.moe> * chore(route/picnob): add myself as maintainer This route explicitly consult CHROMIUM_EXECUTABLE_PATH from the docker image variant `diygod/rsshub:chromium-bundled`. Since I am the author of the image variant, add myself as one of route maintainers so that I am mentioned when the route is broken. Signed-off-by: Rongrong <i@rong.moe> # Conflicts: # lib/routes/picnob/user.ts --------- Signed-off-by: Rongrong <i@rong.moe>
This commit is contained in:
parent
28bc65d0be
commit
3ede128c55
|
|
@ -34,6 +34,16 @@ jobs:
|
|||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Enable ZRAM
|
||||
# Reduce memory pressure
|
||||
# PERCENT=100 is safe: https://fedoraproject.org/wiki/Changes/Scale_ZRAM_to_full_memory_size
|
||||
run: |
|
||||
sudo apt-get update -yq
|
||||
sudo apt-get install -yq "linux-modules-extra-$(uname -r)" zram-tools
|
||||
echo -e 'ALGO=lz4\nPERCENT=100' | sudo tee -a /etc/default/zramswap
|
||||
sudo systemctl restart zramswap
|
||||
swapon
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
|
|
|
|||
10
Dockerfile
10
Dockerfile
|
|
@ -132,6 +132,7 @@ ARG PUPPETEER_SKIP_DOWNLOAD=1
|
|||
# https://www.debian.org/releases/bookworm/amd64/release-notes/ch-information.en.html#noteworthy-obsolete-packages
|
||||
# The official recommended way to use Puppeteer on arm/arm64 is to install Chromium from the distribution repositories:
|
||||
# https://github.com/puppeteer/puppeteer/blob/07391bbf5feaf85c191e1aa8aa78138dce84008d/packages/puppeteer-core/src/node/BrowserFetcher.ts#L128-L131
|
||||
# Dependencies of puppeteer-real-browser: xvfb, procps
|
||||
RUN \
|
||||
set -ex && \
|
||||
apt-get update && \
|
||||
|
|
@ -148,10 +149,13 @@ RUN \
|
|||
; \
|
||||
else \
|
||||
apt-get install -yq --no-install-recommends \
|
||||
chromium xvfb \
|
||||
chromium \
|
||||
&& \
|
||||
echo "CHROMIUM_EXECUTABLE_PATH=$(which chromium)" | tee /app/.env ; \
|
||||
fi; \
|
||||
apt-get install -yq --no-install-recommends \
|
||||
xvfb procps \
|
||||
; \
|
||||
fi; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
@ -161,7 +165,9 @@ RUN \
|
|||
set -ex && \
|
||||
if [ "$PUPPETEER_SKIP_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \
|
||||
echo 'Verifying Chromium installation...' && \
|
||||
if ldd $(find /app/node_modules/.cache/puppeteer/ -name chrome -type f) | grep "not found"; then \
|
||||
_chrome_path=$(find /app/node_modules/.cache/puppeteer/chrome/ -name chrome -xtype f -executable | head -n1) && \
|
||||
echo "CHROMIUM_EXECUTABLE_PATH=$_chrome_path" | tee /app/.env && \
|
||||
if ldd "$_chrome_path" | grep "not found"; then \
|
||||
echo "!!! Chromium has unmet shared libs !!!" && \
|
||||
exit 1 ; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -3,12 +3,42 @@ import { Route, ViewType } from '@/types';
|
|||
import cache from '@/utils/cache';
|
||||
import { parseRelativeDate } from '@/utils/parse-date';
|
||||
import { load } from 'cheerio';
|
||||
import { connect, Options, ConnectResult } from 'puppeteer-real-browser';
|
||||
|
||||
async function getPageWithRealBrowser(url: string, selector: string) {
|
||||
const realBrowserOption: Options = {
|
||||
args: ['--start-maximized'],
|
||||
turnstile: true,
|
||||
headless: false,
|
||||
// disableXvfb: true,
|
||||
// ignoreAllFlags:true,
|
||||
customConfig: {
|
||||
chromePath: config.chromiumExecutablePath,
|
||||
},
|
||||
connectOption: {
|
||||
defaultViewport: null,
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
async function getPageWithRealBrowser(url: string, selector: string, conn: ConnectResult | null) {
|
||||
try {
|
||||
const res = await fetch(`${config.puppeteerRealBrowserService}?url=${encodeURIComponent(url)}&selector=${encodeURIComponent(selector)}`);
|
||||
const json = await res.json();
|
||||
return (json.data?.at(0) || '') as string;
|
||||
if (conn) {
|
||||
const page = conn.page;
|
||||
await page.goto(url, { timeout: 30000 });
|
||||
let verify: boolean | null = null;
|
||||
const startDate = Date.now();
|
||||
while (!verify && Date.now() - startDate < 30000) {
|
||||
// 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));
|
||||
}
|
||||
return await page.content();
|
||||
} else {
|
||||
const res = await fetch(`${config.puppeteerRealBrowserService}?url=${encodeURIComponent(url)}&selector=${encodeURIComponent(selector)}`);
|
||||
const json = await res.json();
|
||||
return (json.data?.at(0) || '') as string;
|
||||
}
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -41,14 +71,14 @@ export const route: Route = {
|
|||
},
|
||||
],
|
||||
name: 'User Profile - Pixnoy',
|
||||
maintainers: ['TonyRL', 'micheal-death', 'AiraNadih', 'DIYgod', 'hyoban'],
|
||||
maintainers: ['TonyRL', 'micheal-death', 'AiraNadih', 'DIYgod', 'hyoban', 'Rongronggg9'],
|
||||
handler,
|
||||
view: ViewType.Pictures,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
if (!config.puppeteerRealBrowserService) {
|
||||
throw new Error('PUPPETEER_REAL_BROWSER_SERVICE is required to use this route.');
|
||||
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
|
||||
|
|
@ -57,8 +87,24 @@ async function handler(ctx) {
|
|||
const type = ctx.req.param('type') ?? 'profile';
|
||||
const profileUrl = `${baseUrl}/profile/${id}/${type === 'tagged' ? 'tagged/' : ''}`;
|
||||
|
||||
const html = await getPageWithRealBrowser(profileUrl, '.post_box');
|
||||
let conn: ConnectResult | null = null;
|
||||
|
||||
if (!config.puppeteerRealBrowserService) {
|
||||
conn = await connect(realBrowserOption);
|
||||
|
||||
setTimeout(async () => {
|
||||
if (conn) {
|
||||
await conn.browser.close();
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
const html = await getPageWithRealBrowser(profileUrl, '.post_box', conn);
|
||||
if (!html) {
|
||||
if (conn) {
|
||||
await conn.browser.close();
|
||||
conn = null;
|
||||
}
|
||||
throw new Error('Failed to fetch user profile page. User may not exist or there are no posts available.');
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +128,23 @@ async function handler(ctx) {
|
|||
};
|
||||
});
|
||||
|
||||
const htmlList = (await Promise.all(list.map((item) => cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => await getPageWithRealBrowser(item.link, '.view'))))) as string[];
|
||||
const jobs = list.map((item) => cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => await getPageWithRealBrowser(item.link, '.view', conn)));
|
||||
|
||||
let htmlList: string[] = [];
|
||||
if (conn) {
|
||||
try {
|
||||
for (const job of jobs) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const html = await job;
|
||||
htmlList.push(html);
|
||||
}
|
||||
} finally {
|
||||
await conn.browser.close();
|
||||
conn = null;
|
||||
}
|
||||
} else {
|
||||
htmlList = await Promise.all(jobs);
|
||||
}
|
||||
|
||||
const newDescription = htmlList.map((html) => {
|
||||
if (!html) {
|
||||
|
|
@ -94,7 +156,10 @@ async function handler(ctx) {
|
|||
} else {
|
||||
let description = '';
|
||||
for (const pic of $('.pic img').toArray()) {
|
||||
description += `<img src="${$(pic).attr('data-src')}" /><br />`;
|
||||
const dataSrc = $(pic).attr('data-src');
|
||||
if (dataSrc) {
|
||||
description += `<img src="${dataSrc}" /><br />`;
|
||||
}
|
||||
}
|
||||
description += $('.sum_full').text();
|
||||
return description;
|
||||
|
|
|
|||
Loading…
Reference in New Issue