mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-03-13 10:30:18 +08:00
feat: redirect /picnob to /picnob.info
This commit is contained in:
@@ -12,7 +12,6 @@ services:
|
||||
CACHE_TYPE: redis
|
||||
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']
|
||||
interval: 30s
|
||||
@@ -22,17 +21,6 @@ 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
|
||||
|
||||
@@ -25,7 +25,7 @@ export const route: Route = {
|
||||
target: '/manga/:id',
|
||||
},
|
||||
],
|
||||
name: 'カドコミ(Kadocomi)漫画详情',
|
||||
name: '漫画详情',
|
||||
maintainers: ['xiaobailoves'],
|
||||
|
||||
handler: async (ctx) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import wait from '@/utils/wait';
|
||||
|
||||
import type { Post, Profile, Pull, Status, Story } from './types';
|
||||
|
||||
@@ -105,7 +106,7 @@ async function handler(ctx) {
|
||||
}
|
||||
if (attempt < 9) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
await wait(3000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import { load } from 'cheerio';
|
||||
import type { ConnectResult, Options } from 'puppeteer-real-browser';
|
||||
import { connect } from 'puppeteer-real-browser';
|
||||
// import { load } from 'cheerio';
|
||||
// import type { ConnectResult, Options } from 'puppeteer-real-browser';
|
||||
// import { connect } from 'puppeteer-real-browser';
|
||||
|
||||
import { config } from '@/config';
|
||||
// import { config } from '@/config';
|
||||
import type { Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import { parseRelativeDate } from '@/utils/parse-date';
|
||||
// import cache from '@/utils/cache';
|
||||
// import { parseRelativeDate } from '@/utils/parse-date';
|
||||
|
||||
const realBrowserOption: Options = {
|
||||
args: ['--start-maximized'],
|
||||
turnstile: true,
|
||||
headless: false,
|
||||
// disableXvfb: true,
|
||||
// ignoreAllFlags:true,
|
||||
customConfig: {
|
||||
chromePath: config.chromiumExecutablePath,
|
||||
},
|
||||
connectOption: {
|
||||
defaultViewport: null,
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
// 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 {
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
// async function getPageWithRealBrowser(url: string, selector: string, conn: ConnectResult | null) {
|
||||
// try {
|
||||
// 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 '';
|
||||
// }
|
||||
// }
|
||||
|
||||
export const route: Route = {
|
||||
path: '/user/:id/:type?',
|
||||
@@ -57,8 +57,8 @@ export const route: Route = {
|
||||
},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: true,
|
||||
antiCrawler: true,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
@@ -79,104 +79,108 @@ export const route: Route = {
|
||||
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';
|
||||
function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
const type = ctx.req.param('type') ?? 'profile';
|
||||
const profileUrl = `${baseUrl}/profile/${id}/${type === 'tagged' ? 'tagged/' : ''}`;
|
||||
return ctx.set('redirect', `/picnob.info/user/${id}`);
|
||||
|
||||
let conn: ConnectResult | null = null;
|
||||
// // Original puppeteer-real-browser implementation (deprecated)
|
||||
// if (!config.puppeteerRealBrowserService && !config.chromiumExecutablePath) {
|
||||
// throw new Error('PUPPETEER_REAL_BROWSER_SERVICE or CHROMIUM_EXECUTABLE_PATH is required to use this route.');
|
||||
// }
|
||||
|
||||
if (!config.puppeteerRealBrowserService) {
|
||||
conn = await connect(realBrowserOption);
|
||||
// // NOTE: 'picnob' is still available, but all requests to 'picnob' will be redirected to 'picnob.info' 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/' : ''}`;
|
||||
|
||||
setTimeout(async () => {
|
||||
if (conn) {
|
||||
await conn.browser.close();
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
// let conn: ConnectResult | null = null;
|
||||
|
||||
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.');
|
||||
}
|
||||
// if (!config.puppeteerRealBrowserService) {
|
||||
// conn = await connect(realBrowserOption);
|
||||
|
||||
const $ = load(html);
|
||||
// setTimeout(async () => {
|
||||
// if (conn) {
|
||||
// await conn.browser.close();
|
||||
// }
|
||||
// }, 60000);
|
||||
// }
|
||||
|
||||
const list = $('.post_box')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
const $item = $(item);
|
||||
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') || '';
|
||||
// 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.');
|
||||
// }
|
||||
|
||||
return {
|
||||
title,
|
||||
description: `<img src="${image.attr('data-src')}" /><br />${title}`,
|
||||
link: `${baseUrl}${coverLink}`,
|
||||
guid: shortcode,
|
||||
pubDate: parseRelativeDate($item.find('.time .txt').text()),
|
||||
};
|
||||
});
|
||||
// const $ = load(html);
|
||||
|
||||
const jobs = list.map((item) => cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => await getPageWithRealBrowser(item.link, '.view', conn)));
|
||||
// const list = $('.post_box')
|
||||
// .toArray()
|
||||
// .map((item) => {
|
||||
// const $item = $(item);
|
||||
// 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') || '';
|
||||
|
||||
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);
|
||||
}
|
||||
// return {
|
||||
// title,
|
||||
// description: `<img src="${image.attr('data-src')}" /><br />${title}`,
|
||||
// link: `${baseUrl}${coverLink}`,
|
||||
// guid: shortcode,
|
||||
// pubDate: parseRelativeDate($item.find('.time .txt').text()),
|
||||
// };
|
||||
// });
|
||||
|
||||
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 pic of $('.pic img').toArray()) {
|
||||
const dataSrc = $(pic).attr('data-src');
|
||||
if (dataSrc) {
|
||||
description += `<img src="${dataSrc}" /><br />`;
|
||||
}
|
||||
}
|
||||
description += $('.sum_full').text();
|
||||
return description;
|
||||
}
|
||||
});
|
||||
// const jobs = list.map((item) => cache.tryGet(`picnob:user:${id}:${item.guid}:html`, async () => await getPageWithRealBrowser(item.link, '.view', conn)));
|
||||
|
||||
return {
|
||||
title: `${$('h1.fullname').text()} (@${id}) ${type === 'tagged' ? 'tagged' : 'public'} posts - Picnob`,
|
||||
description: $('.info .sum').text(),
|
||||
link: profileUrl,
|
||||
image: $('.ava .pic img').attr('src'),
|
||||
item: list.map((item, index) => ({
|
||||
...item,
|
||||
description: newDescription[index] || item.description,
|
||||
})),
|
||||
};
|
||||
// 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) {
|
||||
// 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 pic of $('.pic img').toArray()) {
|
||||
// const dataSrc = $(pic).attr('data-src');
|
||||
// if (dataSrc) {
|
||||
// description += `<img src="${dataSrc}" /><br />`;
|
||||
// }
|
||||
// }
|
||||
// description += $('.sum_full').text();
|
||||
// return description;
|
||||
// }
|
||||
// });
|
||||
|
||||
// return {
|
||||
// title: `${$('h1.fullname').text()} (@${id}) ${type === 'tagged' ? 'tagged' : 'public'} posts - Picnob`,
|
||||
// description: $('.info .sum').text(),
|
||||
// link: profileUrl,
|
||||
// image: $('.ava .pic img').attr('src'),
|
||||
// item: list.map((item, index) => ({
|
||||
// ...item,
|
||||
// description: newDescription[index] || item.description,
|
||||
// })),
|
||||
// };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user