UNPKG

rsshub

Version:
169 lines (164 loc) 7.29 kB
import { t as logger_default } from "./logger-Czu8UMNd.mjs"; import { t as parseDate } from "./parse-date-BrP7mxXf.mjs"; import { t as cache_default } from "./cache-Bo__VnGm.mjs"; import { t as got_default } from "./got-KxxWdaxq.mjs"; import { i as maskHeader, r as pixiv_got_default, t as utils_default } from "./utils-BFdHMhIn.mjs"; import { load } from "cheerio"; import { JSDOM, VirtualConsole } from "jsdom"; import queryString from "query-string"; //#region lib/routes/pixiv/api/get-illust-detail.ts /** * 获取插画详细信息 * @param {string} illust_id 插画作品 id * @param {string} token pixiv oauth token * @returns {Promise<got.AxiosResponse<{illust: IllustDetail}>>} */ function getIllustDetail(illust_id, token) { return pixiv_got_default("https://app-api.pixiv.net/v1/illust/detail", { headers: { ...maskHeader, Authorization: "Bearer " + token }, searchParams: queryString.stringify({ illust_id, filter: "for_ios" }) }); } //#endregion //#region lib/routes/pixiv/novel-api/content/utils.ts function convertPixivProtocolExtended(caption) { const protocolMap = new Map([ [/pixiv:\/\/novels\/(\d+)/g, "https://www.pixiv.net/novel/show.php?id=$1"], [/pixiv:\/\/illusts\/(\d+)/g, "https://www.pixiv.net/artworks/$1"], [/pixiv:\/\/users\/(\d+)/g, "https://www.pixiv.net/users/$1"], [/pixiv:\/\/novel\/series\/(\d+)/g, "https://www.pixiv.net/novel/series/$1"] ]); let convertedText = caption; for (const [pattern, replacement] of protocolMap) convertedText = convertedText.replace(pattern, replacement); return convertedText; } async function parseNovelContent(content, images, token) { try { if (token) { const imageMatches = [...content.matchAll(/\[pixivimage:(\d+)(?:-(\d+))?\]/g)]; const imageIdToUrl = /* @__PURE__ */ new Map(); await Promise.all(imageMatches.map(async ([, illustId, pageNum]) => { if (!illustId) return; try { const illust = (await getIllustDetail(illustId, token)).data.illust; const imageUrl = utils_default.getImgs(illust).map((img) => img.match(/src="([^"]+)"/)?.[1] || "")[Number(pageNum) || 0]; if (imageUrl) imageIdToUrl.set(pageNum ? `${illustId}-${pageNum}` : illustId, imageUrl); } catch (error) { logger_default.warn(`Failed to fetch illust detail for ID ${illustId}: ${error instanceof Error ? error.message : String(error)}`); } })); content = content.replaceAll(/\[pixivimage:(\d+)(?:-(\d+))?\]/g, (match, illustId, pageNum) => { const key = pageNum ? `${illustId}-${pageNum}` : illustId; const imageUrl = imageIdToUrl.get(key); return imageUrl ? `<img src="${imageUrl}" alt="pixiv illustration ${illustId}${pageNum ? ` page ${pageNum}` : ""}">` : match; }); } else content = content.replaceAll(/\[pixivimage:(\d+)(?:-(\d+))?\]/g, (_, illustId) => `<a href="https://www.pixiv.net/artworks/${illustId}" target="_blank" rel="noopener noreferrer">Pixiv Artwork #${illustId}</a>`); content = content.replaceAll(/\[uploadedimage:(\d+)\]/g, (match, imageId) => { if (images[imageId]) return `<img src="${utils_default.getProxiedImageUrl(images[imageId])}" alt="novel illustration ${imageId}">`; return match; }); content = content.replaceAll("\n", "<br>").replaceAll(/(<br>){2,}/g, "</p><p>").replaceAll(/\[\[rb:(.*?)>(.*?)\]\]/g, "<ruby>$1<rt>$2</rt></ruby>").replaceAll(/\[\[jumpuri:(.*?)>(.*?)\]\]/g, "<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>").replaceAll(/\[jump:(\d+)\]/g, "Jump to page $1").replaceAll(/\[chapter:(.*?)\]/g, "<h2>$1</h2>").replaceAll("[newpage]", "<hr>"); const $content = load(`<article><p>${content}</p></article>`); $content("p p").each((_, elem) => { const $elem = $content(elem); $elem.replaceWith($elem.html() || ""); }); $content("p h2").each((_, elem) => { const $elem = $content(elem); const $parent = $elem.parent("p"); const html = $elem.prop("outerHTML"); if ($parent.length && html) $parent.replaceWith(`</p>${html}<p>`); }); return $content.html() || ""; } catch (error) { throw new Error(`Error parsing novel content: ${error instanceof Error ? error.message : String(error)}`); } } //#endregion //#region lib/routes/pixiv/novel-api/content/nsfw.ts async function getNSFWNovelContent(novelId, token) { return await cache_default.tryGet(`https://app-api.pixiv.net/webview/v2/novel:${novelId}`, async () => { const response = await pixiv_got_default("https://app-api.pixiv.net/webview/v2/novel", { headers: { ...maskHeader, Authorization: "Bearer " + token }, searchParams: queryString.stringify({ id: novelId, viewer_version: "20221031_ai" }) }); const virtualConsole = new VirtualConsole().on("error", () => void 0); const { window } = new JSDOM(response.data, { runScripts: "dangerously", virtualConsole }); const novelDetail = window.pixiv?.novel; window.close(); if (!novelDetail) throw new Error("No novel data found"); const images = Object.fromEntries(Object.entries(novelDetail.images).filter(([, image]) => image?.urls?.original).map(([id, image]) => [id, image.urls.original])); const parsedContent = await parseNovelContent(novelDetail.text, images, token); return { id: novelDetail.id, title: novelDetail.title, description: novelDetail.caption, content: parsedContent, userId: novelDetail.userId, userName: null, bookmarkCount: novelDetail.rating.bookmark, viewCount: novelDetail.rating.view, likeCount: novelDetail.rating.like, createDate: parseDate(novelDetail.cdate), updateDate: null, isOriginal: novelDetail.isOriginal, aiType: novelDetail.aiType, tags: novelDetail.tags, coverUrl: novelDetail.coverUrl, images, seriesId: novelDetail.seriesId || null, seriesTitle: novelDetail.seriesTitle || null }; }); } //#endregion //#region lib/routes/pixiv/novel-api/content/sfw.ts const baseUrl = "https://www.pixiv.net"; async function getSFWNovelContent(novelId) { const url = `${baseUrl}/ajax/novel/${novelId}`; return await cache_default.tryGet(url, async () => { const novelDetail = (await got_default(url, { headers: { referer: `${baseUrl}/novel/show.php?id=${novelId}` } })).data; if (!novelDetail) throw new Error("No novel data found"); const body = novelDetail.body; const images = {}; if (novelDetail.body.textEmbeddedImages) for (const [id, image] of Object.entries(novelDetail.body.textEmbeddedImages)) images[id] = utils_default.getProxiedImageUrl(image.urls.original); const parsedContent = await parseNovelContent(novelDetail.body.content, images); return { id: body.id, title: body.title, description: body.description, content: parsedContent, userId: body.userId, userName: body.userName, bookmarkCount: body.bookmarkCount, viewCount: body.viewCount, likeCount: body.likeCount, createDate: parseDate(body.createDate), updateDate: parseDate(body.uploadDate), isOriginal: body.isOriginal, aiType: body.aiType, tags: body.tags.tags.map((tag) => tag.tag), coverUrl: body.coverUrl, images, seriesId: body.seriesNavData?.seriesId?.toString() || null, seriesTitle: body.seriesNavData?.title || null }; }); } //#endregion export { getNSFWNovelContent as n, convertPixivProtocolExtended as r, getSFWNovelContent as t };