mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-24 23:12:34 +08:00
feat: add pixiv-fanbox (#5532)
This commit is contained in:
@@ -115,6 +115,18 @@ Only for self-hosted
|
||||
:::
|
||||
</RouteEn>
|
||||
|
||||
## pixiv-fanbox
|
||||
|
||||
<RouteEn author="sgqy" example="/fanbox/otomeoto" path="/fanbox/:user?" :paramsDesc="['User name. Can be found in URL. Default is official news']"/>
|
||||
::: warning Paywall
|
||||
|
||||
The site **fanbox** is made for paid users. To get paid content, you should host by yourself.
|
||||
|
||||
Settings: environment argument `FANBOX_SESSION_ID` equals to `FANBOXSESSID` in site cookies.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## Telegram
|
||||
|
||||
### Channel
|
||||
|
||||
@@ -403,6 +403,17 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
|
||||
:::
|
||||
</Route>
|
||||
|
||||
## pixiv-fanbox
|
||||
|
||||
<Route author="sgqy" example="/fanbox/otomeoto" path="/fanbox/:user?" :paramsDesc="['用户名, 可在用户主页 URL 中找到. 默认为官方资讯']"/>
|
||||
::: warning 付费墙注意
|
||||
|
||||
由于 fanbox 专为付费用户设计,要查看已经付费的内容只能自建。
|
||||
|
||||
自建设置:环境变量`FANBOX_SESSION_ID`对应 cookies 中的`FANBOXSESSID`。
|
||||
|
||||
:::
|
||||
|
||||
## Popi 提问箱
|
||||
|
||||
### 提问箱新回答
|
||||
|
||||
@@ -56,6 +56,9 @@ const calculateValue = () => {
|
||||
username: envs.PIXIV_USERNAME,
|
||||
password: envs.PIXIV_PASSWORD,
|
||||
},
|
||||
fanbox: {
|
||||
session: envs.FANBOX_SESSION_ID,
|
||||
},
|
||||
disqus: {
|
||||
api_key: envs.DISQUS_API_KEY,
|
||||
},
|
||||
|
||||
@@ -162,6 +162,9 @@ router.get('/pixiv/user/:id', require('./routes/pixiv/user'));
|
||||
router.get('/pixiv/ranking/:mode/:date?', require('./routes/pixiv/ranking'));
|
||||
router.get('/pixiv/search/:keyword/:order?/:r18?', require('./routes/pixiv/search'));
|
||||
|
||||
// pixiv-fanbox
|
||||
router.get('/fanbox/:user?', require('./routes/fanbox/main'));
|
||||
|
||||
// 豆瓣
|
||||
router.get('/douban/movie/playing', require('./routes/douban/playing'));
|
||||
router.get('/douban/movie/playing/:score', require('./routes/douban/playing'));
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
const got = require('@/utils/got');
|
||||
|
||||
const get_header = require('./header');
|
||||
|
||||
async function get_twitter(t) {
|
||||
try {
|
||||
const resp = await got(`https://publish.twitter.com/oembed?url=${t}`);
|
||||
return resp.data.html;
|
||||
} catch (_) {
|
||||
return `<div style="border-style:solid;border-width:0.5px;padding:0.5em;">This tweet may not exist</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function get_fanbox(p) {
|
||||
try {
|
||||
const m = p.match(/creator\/([0-9]+)\/post\/([0-9]+)/);
|
||||
const post_id = m[2];
|
||||
const api_url = `https://api.fanbox.cc/post.info?postId=${post_id}`;
|
||||
const resp = await got(api_url, { headers: get_header() });
|
||||
const post = resp.data.body;
|
||||
|
||||
const home_url = `https://${post.creatorId}.fanbox.cc`;
|
||||
const web_url = `${home_url}/posts/${post.id}`;
|
||||
const datetime = new Date(post.updatedDatetime).toLocaleString('ja');
|
||||
|
||||
const box_html = `
|
||||
<div style="width:640;padding:0.5em;border-style:solid;border-width:0.5px">
|
||||
<img width=300 src="${post.imageForShare}" />
|
||||
<br/>
|
||||
<a href="${web_url}" style="font-weight:bold">${post.title}</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="${home_url}" style="margin-right:1.6em">${post.user.name}</a>
|
||||
<span style="margin-right:1.6em">Modify: ${datetime}</span>
|
||||
<span>${post.feeRequired} JPY</span>
|
||||
</div>
|
||||
`;
|
||||
return { url: web_url, html: box_html };
|
||||
} catch (_) {
|
||||
return { url: null, html: `<div style="border-style:solid;border-width:0.5px;padding:0.5em;">fanbox post (${p}) may not exist</div>` };
|
||||
}
|
||||
}
|
||||
|
||||
// embedded items
|
||||
async function embed_map(e) {
|
||||
const id = e.contentId || e.videoId;
|
||||
const sp = e.serviceProvider;
|
||||
|
||||
let ret = `Unknown host: ${sp}, with ID: ${id}`;
|
||||
let url = null;
|
||||
|
||||
try {
|
||||
switch (sp) {
|
||||
case 'youtube':
|
||||
url = `https://www.youtube.com/embed/${id}`;
|
||||
ret = `<iframe type="text/html" width="640" height="360" src="${url}" frameborder="0" allowfullscreen></iframe>`;
|
||||
break;
|
||||
case 'vimeo':
|
||||
url = `https://player.vimeo.com/video/${id}`;
|
||||
ret = `<iframe type="text/html" width="640" height="360" src="${url}" frameborder="0" allowfullscreen></iframe>`;
|
||||
break;
|
||||
case 'soundcloud':
|
||||
url = `https://soundcloud.com/${id}`;
|
||||
ret = `<iframe width="640" height="166" frameborder="0" allowfullscreen src="https://w.soundcloud.com/player/?url=${url}"></iframe>`;
|
||||
break;
|
||||
case 'twitter':
|
||||
url = `https://twitter.com/i/status/${id}`;
|
||||
ret = await get_twitter(url);
|
||||
break;
|
||||
case 'google_forms':
|
||||
url = `https://docs.google.com/forms/d/e/${id}/viewform?embedded=true`;
|
||||
ret = `<iframe src="${url}" width=640 height=800 frameborder="0" allowfullscreen></iframe>`;
|
||||
break;
|
||||
case 'fanbox': {
|
||||
const info = await get_fanbox(id);
|
||||
url = info.url;
|
||||
ret = info.html;
|
||||
break;
|
||||
}
|
||||
case 'gist':
|
||||
url = `https://gist.github.com/${id}`;
|
||||
ret = `<iframe frameborder="0" width="640" height="480" srcdoc="<script src=${url}.js></script>"></iframe>`;
|
||||
break;
|
||||
}
|
||||
if (url) {
|
||||
ret += `<br/><a href="${url}" style="font-size:0.6em;">Click here if embedded content is not loaded.</a>`;
|
||||
}
|
||||
} catch (_) {
|
||||
_;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// render <p/> blocks
|
||||
function passage_conv(p) {
|
||||
const seg = p.text.split('');
|
||||
// seg.push('');
|
||||
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;
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
const ret = seg.join('');
|
||||
// console.log(ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// article types
|
||||
function text_t(body) {
|
||||
return body.text || '';
|
||||
}
|
||||
|
||||
function image_t(body) {
|
||||
let ret = body.text || '';
|
||||
body.images.map((i) => (ret += `<hr><img src="${i.originalUrl}">`));
|
||||
return ret;
|
||||
}
|
||||
|
||||
function file_t(body) {
|
||||
let ret = body.text || '';
|
||||
body.files.map((f) => (ret += `<br><a href="${f.url}" download="${f.name}.${f.extension}">${f.name}.${f.extension}</a>`));
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function video_t(body) {
|
||||
let ret = body.text || '';
|
||||
ret += (await embed_map(body.video)) || '';
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function blog_t(body) {
|
||||
let ret = [];
|
||||
for (let x = 0; x < body.blocks.length; ++x) {
|
||||
const b = body.blocks[x];
|
||||
ret.push('<p>');
|
||||
|
||||
switch (b.type) {
|
||||
case 'p':
|
||||
ret.push(passage_conv(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 f = body.fileMap[b.fileId];
|
||||
ret.push(`<a href="${f.url}" download="${f.name}.${f.extension}">${f.name}.${f.extension}</a>`);
|
||||
break;
|
||||
}
|
||||
case 'embed':
|
||||
ret.push(embed_map(body.embedMap[b.embedId])); // Promise object
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret = await Promise.all(ret); // get real data
|
||||
return ret.join('');
|
||||
}
|
||||
|
||||
// parse by type
|
||||
async function conv_article(i) {
|
||||
let ret = '';
|
||||
if (i.title) {
|
||||
ret += `[${i.type}] ${i.title}<hr>`;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// console.log(i);
|
||||
// skip paywall
|
||||
|
||||
switch (i.type) {
|
||||
case 'text':
|
||||
ret += text_t(i.body);
|
||||
break;
|
||||
case 'file':
|
||||
ret += file_t(i.body);
|
||||
break;
|
||||
case 'image':
|
||||
ret += image_t(i.body);
|
||||
break;
|
||||
case 'video':
|
||||
ret += await video_t(i.body);
|
||||
break;
|
||||
case 'article':
|
||||
ret += await blog_t(i.body);
|
||||
break;
|
||||
default:
|
||||
ret += '<b>Unsupported content (RSSHub)</b>';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// render wrapper
|
||||
module.exports = async (i) => ({
|
||||
title: i.title || `No title`,
|
||||
description: await conv_article(i),
|
||||
pubDate: new Date(i.publishedDatetime).toUTCString(),
|
||||
link: `https://${i.creatorId}.fanbox.cc/posts/${i.id}`,
|
||||
category: i.tags,
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
const config = require('@/config').value;
|
||||
|
||||
// unlock contents paid by user
|
||||
module.exports = () => {
|
||||
const sessid = config.fanbox.session;
|
||||
let cookie = '';
|
||||
if (sessid) {
|
||||
cookie += `FANBOXSESSID=${sessid}`;
|
||||
}
|
||||
const headers = { origin: 'https://fanbox.cc', cookie: cookie };
|
||||
|
||||
return headers;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// pixiv fanbox, maybe blocked by upstream
|
||||
|
||||
// params:
|
||||
// user?: fanbox domain name
|
||||
|
||||
const got = require('@/utils/got');
|
||||
|
||||
const conv_item = require('./conv');
|
||||
const get_header = require('./header');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const user = ctx.params.user || 'official'; // if no user specified, just go to official page
|
||||
const box_url = `https://${user}.fanbox.cc`;
|
||||
|
||||
// get user info
|
||||
let title = `${user}'s fanbox`;
|
||||
let descr = title;
|
||||
|
||||
try {
|
||||
const user_api = `https://api.fanbox.cc/creator.get?creatorId=${user}`;
|
||||
const resp_u = await got(user_api, { headers: get_header() });
|
||||
title = `${resp_u.data.body.user.name}'s fanbox`;
|
||||
descr = resp_u.data.description;
|
||||
} catch (_) {
|
||||
_;
|
||||
}
|
||||
|
||||
// get user posts
|
||||
const posts_api = `https://api.fanbox.cc/post.listCreator?creatorId=${user}&limit=20`;
|
||||
const response = await got(posts_api, { headers: get_header() });
|
||||
|
||||
// render posts
|
||||
const items = await Promise.all(response.data.body.items.map((i) => conv_item(i)));
|
||||
|
||||
// return rss feed
|
||||
ctx.state.data = {
|
||||
title: title,
|
||||
link: box_url,
|
||||
description: descr,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user