rsshub
Version:
Make RSS Great Again!
239 lines (238 loc) • 7.35 kB
JavaScript
import { t as rofetch } from "./ofetch-C3ts-Hud.mjs";
import { t as config } from "./config-CCmw1BNE.mjs";
import { t as parseDate } from "./parse-date-Cr0wQjV_.mjs";
import { t as cache_default } from "./cache-BkqOokyU.mjs";
import { t as InvalidParameterError } from "./invalid-parameter-CGxhjomn.mjs";
import { t as isValidHost } from "./valid-host-BH9Dd2Pf.mjs";
import { n as outPlaywright } from "./playwright-o20DiLNh.mjs";
import { n as setCookies } from "./playwright-utils-BWuSHbXD.mjs";
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
import { renderToString } from "hono/jsx/dom/server";
//#region lib/routes/fanbox/utils.tsx
function getHeaders() {
return {
origin: "https://fanbox.cc",
cookie: getCookieString()
};
}
function getCookieString() {
const sessionid = config.fanbox.session;
return sessionid ? `FANBOXSESSID=${sessionid}` : "";
}
function embedUrlMap(urlEmbed) {
switch (urlEmbed.type) {
case "html": return urlEmbed.html;
case "fanbox.post": return renderToString(/* @__PURE__ */ jsxs("a", {
href: `https://${urlEmbed.postInfo.creatorId}.fanbox.cc/posts/${urlEmbed.postInfo.id}`,
children: [
/* @__PURE__ */ jsx("h1", { children: urlEmbed.postInfo.title }),
/* @__PURE__ */ jsx("span", {
style: "margin-right:1.6em",
children: urlEmbed.postInfo.user.name
}),
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsx("span", { children: urlEmbed.postInfo.excerpt })
]
}));
default: return "";
}
}
function passageConv(p) {
const seg = [...p.text];
if (p.styles) p.styles.map((s) => {
switch (s.type) {
case "bold":
seg[s.offset] = "<b>" + seg[s.offset];
seg[s.offset + s.length - 1] += "</b>";
break;
default:
}
return s;
});
if (p.links) p.links.map((l) => {
seg[l.offset] = `<a href="${l.url}">` + seg[l.offset];
seg[l.offset + l.length - 1] += "</a>";
return l;
});
return seg.join("");
}
function parseText(body) {
return body.text || "";
}
function parseImage(body) {
let ret = body.text || "";
for (const i of body.images) ret += `<hr><img src="${i.originalUrl}">`;
return ret;
}
function parseFile(body) {
let ret = body.text || "";
for (const f of body.files) ret += `<br><a href="${f.url}" download="${f.name}.${f.extension}">${f.name}.${f.extension}</a>`;
return ret;
}
async function parseVideo(body) {
let ret = "";
switch (body.video.serviceProvider) {
case "soundcloud":
ret += await getSoundCloudEmbedUrl(body.video.videoId);
break;
case "youtube":
ret += `<iframe src="https://www.youtube-nocookie.com/embed/${body.video.videoId}" frameborder="0" referrerpolicy="strict-origin-when-cross-origin"></iframe>`;
break;
case "vimeo":
ret += `<iframe src="https://player.vimeo.com/video/${body.video.videoId}" frameborder="0"></iframe>`;
break;
default:
}
ret += `<br>${body.text}`;
return ret;
}
async function parseArtile(body) {
let ret = [];
for (const b of body.blocks) {
ret.push("<p>");
switch (b.type) {
case "p":
ret.push(passageConv(b));
break;
case "header":
ret.push(`<h2>${b.text}</h2>`);
break;
case "image": {
const i = body.imageMap[b.imageId];
ret.push(`<img src="${i.originalUrl}">`);
break;
}
case "file": {
const file = body.fileMap[b.fileId];
ret.push(`<a href="${file.url}" download="${file.name}.${file.extension}">${file.name}.${file.extension}</a>`);
break;
}
case "url_embed":
ret.push(embedUrlMap(body.urlEmbedMap[b.urlEmbedId]));
break;
default:
}
}
ret = await Promise.all(ret);
return ret.join("");
}
async function parseDetail(i) {
let ret = "";
if (i.feeRequired !== 0) ret += `Fee Required: <b>${i.feeRequired} JPY/month</b><hr>`;
if (i.coverImageUrl) ret += `<img src="${i.coverImageUrl}"><hr>`;
if (!i.body) {
ret += i.excerpt;
return ret;
}
switch (i.type) {
case "text":
ret += parseText(i.body);
break;
case "file":
ret += parseFile(i.body);
break;
case "image":
ret += parseImage(i.body);
break;
case "video":
ret += await parseVideo(i.body);
break;
case "article":
ret += await parseArtile(i.body);
break;
default: ret += "<b>Unsupported content (RSSHub)</b>";
}
return ret;
}
function parseItem(page, item) {
return cache_default.tryGet(`fanbox-${item.id}-${item.updatedDatetime}`, async () => {
const postDetail = await page.evaluate(async ({ url }) => {
const res = await fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
}
});
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
}, { url: `https://api.fanbox.cc/post.info?postId=${item.id}` });
return {
title: item.title || "No title",
description: await parseDetail(postDetail.body.post),
pubDate: parseDate(item.updatedDatetime),
link: `https://${item.creatorId}.fanbox.cc/posts/${item.id}`,
category: item.tags
};
});
}
async function getSoundCloudEmbedUrl(videoId) {
const videoUrl = `https://soundcloud.com/${videoId}`;
return (await rofetch(`https://soundcloud.com/oembed?url=${encodeURIComponent(videoUrl)}&format=json&maxheight=400&format=json`)).html;
}
//#endregion
//#region lib/routes/fanbox/index.ts
const route = {
path: "/:creator",
categories: ["social-media"],
example: "/fanbox/official",
parameters: { creator: "fanbox user name" },
maintainers: ["KarasuShin", "pseudoyu"],
name: "Creator",
handler,
features: {
requireConfig: [{
name: "FANBOX_SESSION_ID",
description: "Required for private posts. Can be found in browser DevTools -> Application -> Cookies -> https://www.fanbox.cc -> FANBOXSESSID",
optional: true
}],
requirePuppeteer: true,
nsfw: true
}
};
async function handler(ctx) {
const creator = ctx.req.param("creator");
if (!isValidHost(creator)) throw new InvalidParameterError("Invalid user name");
let title = `Fanbox - ${creator}`;
let description;
let image;
try {
const userInfoResponse = await rofetch(`https://api.fanbox.cc/creator.get?creatorId=${creator}`, { headers: getHeaders() });
title = `Fanbox - ${userInfoResponse.body.user.name}`;
description = userInfoResponse.body.description;
image = userInfoResponse.body.user.iconUrl;
} catch {}
const postListResponse = await rofetch(`https://api.fanbox.cc/post.listCreator?creatorId=${creator}&limit=20&withPinned=true`, { headers: getHeaders() });
const context = await outPlaywright();
const page = await context.newPage();
const cookieString = getCookieString();
if (cookieString) await setCookies(page, cookieString, ".fanbox.cc");
await page.route("**/*", (route) => {
const request = route.request();
if (request.url().startsWith("https://api.fanbox.cc/post.info")) {
route.continue();
return;
}
request.resourceType() === "document" ? route.continue() : route.abort();
});
await page.goto("https://www.fanbox.cc/", { waitUntil: "domcontentloaded" });
let items;
try {
items = await Promise.all(postListResponse.body.posts.map((i) => parseItem(page, i)));
} finally {
await page.close();
await context.close();
}
return {
title,
link: `https://${creator}.fanbox.cc`,
description,
image,
item: items
};
}
//#endregion
export { route };