refactor(route/coomer): complete route rewrite based on kemono (#18290)
* refactor!(route/coomer): complete route rewrite based on kemono * fix(route/coomer): fix items parsing logic
This commit is contained in:
parent
abc1a579a4
commit
191296e385
|
|
@ -1,33 +0,0 @@
|
|||
import { Route } from '@/types';
|
||||
import fetchItems from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/artist/:id',
|
||||
categories: ['multimedia'],
|
||||
example: '/coomer/artist/belledelphine',
|
||||
parameters: { id: 'Artist id, can be found in URL' },
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['coomer.su/onlyfans/user/:id', 'coomer.su/'],
|
||||
},
|
||||
],
|
||||
name: 'Artist',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
|
||||
const currentUrl = `onlyfans/user/${id}`;
|
||||
|
||||
return await fetchItems(ctx, currentUrl);
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
import { Route } from '@/types';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { art } from '@/utils/render';
|
||||
import path from 'node:path';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:source?/:id?',
|
||||
categories: ['multimedia'],
|
||||
example: '/coomer',
|
||||
parameters: { source: 'Source, see below, Posts by default', id: 'User id, can be found in URL' },
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['coomer.su/:source/user/:id', 'coomer.su/'],
|
||||
},
|
||||
],
|
||||
name: 'Posts',
|
||||
maintainers: ['nczitzk', 'AiraNadih'],
|
||||
handler,
|
||||
description: `Sources
|
||||
|
||||
| Posts | OnlyFans | Fansly | CandFans |
|
||||
| ----- | -------- | ------- | -------- |
|
||||
| posts | onlyfans | fansly | candfans |
|
||||
|
||||
::: tip
|
||||
When \`posts\` is selected as the value of the parameter **source**, the parameter **id** does not take effect.
|
||||
There is an optinal parameter **limit** which controls the number of posts to fetch, default value is 25.
|
||||
:::`,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25;
|
||||
const source = ctx.req.param('source') ?? 'posts';
|
||||
const id = ctx.req.param('id');
|
||||
const isPosts = source === 'posts';
|
||||
|
||||
const rootUrl = 'https://coomer.su';
|
||||
const apiUrl = `${rootUrl}/api/v1`;
|
||||
const currentUrl = isPosts ? `${apiUrl}/posts` : `${apiUrl}/${source}/user/${id}`;
|
||||
|
||||
const headers = {
|
||||
cookie: '__ddg2=sBQ4uaaGecmfEUk7',
|
||||
};
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: currentUrl,
|
||||
headers,
|
||||
});
|
||||
const responseData = isPosts ? response.data.posts : response.data;
|
||||
|
||||
const author = isPosts ? '' : await getAuthor(currentUrl, headers);
|
||||
const title = isPosts ? 'Coomer Posts' : `Posts of ${author} from ${source} | Coomer`;
|
||||
const image = isPosts ? `${rootUrl}/favicon.ico` : `https://img.coomer.su/icons/${source}/${id}`;
|
||||
const items = responseData
|
||||
.filter((i) => i.content || i.attachments)
|
||||
.slice(0, limit)
|
||||
.map((i) => {
|
||||
i.files = [];
|
||||
if ('path' in i.file) {
|
||||
i.files.push({
|
||||
name: i.file.name,
|
||||
path: i.file.path,
|
||||
extension: i.file.path.replace(/.*\./, '').toLowerCase(),
|
||||
});
|
||||
}
|
||||
for (const attachment of i.attachments) {
|
||||
i.files.push({
|
||||
name: attachment.name,
|
||||
path: attachment.path,
|
||||
extension: attachment.path.replace(/.*\./, '').toLowerCase(),
|
||||
});
|
||||
}
|
||||
const filesHTML = art(path.join(__dirname, 'templates', 'source.art'), { i });
|
||||
let $ = load(filesHTML);
|
||||
const coomerFiles = $('img, a, audio, video').map(function () {
|
||||
return $(this).prop('outerHTML')!;
|
||||
});
|
||||
let desc = '';
|
||||
if (i.content) {
|
||||
desc += `<div>${i.content}</div>`;
|
||||
}
|
||||
$ = load(desc);
|
||||
let count = 0;
|
||||
const regex = /downloads.fanbox.cc/;
|
||||
$('a').each(function () {
|
||||
const link = $(this).attr('href');
|
||||
if (regex.test(link!)) {
|
||||
count++;
|
||||
$(this).replaceWith(coomerFiles[count]);
|
||||
}
|
||||
});
|
||||
desc = (coomerFiles.length > 0 ? coomerFiles[0] : '') + $.html();
|
||||
for (const coomerFile of coomerFiles.slice(count + 1)) {
|
||||
desc += coomerFile;
|
||||
}
|
||||
|
||||
let enclosureInfo = {};
|
||||
load(desc)('audio source, video source').each(function () {
|
||||
const src = $(this).attr('src') ?? '';
|
||||
const mimeType =
|
||||
{
|
||||
m4a: 'audio/mp4',
|
||||
mp3: 'audio/mpeg',
|
||||
mp4: 'video/mp4',
|
||||
}[src.replace(/.*\./, '').toLowerCase()] || null;
|
||||
|
||||
if (mimeType === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
enclosureInfo = {
|
||||
enclosure_url: new URL(src, rootUrl).toString(),
|
||||
enclosure_type: mimeType,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title: i.title || parseDate(i.published),
|
||||
description: desc,
|
||||
author,
|
||||
pubDate: parseDate(i.published),
|
||||
guid: `${apiUrl}/${i.service}/user/${i.user}/post/${i.id}`,
|
||||
link: `${rootUrl}/${i.service}/user/${i.user}/post/${i.id}`,
|
||||
...enclosureInfo,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title,
|
||||
image,
|
||||
link: isPosts ? `${rootUrl}/posts` : `${rootUrl}/${source}/user/${id}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
||||
async function getAuthor(currentUrl, headers) {
|
||||
const profileResponse = await got({
|
||||
method: 'get',
|
||||
url: `${currentUrl}/profile`,
|
||||
headers,
|
||||
});
|
||||
return profileResponse.data.name;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { Route } from '@/types';
|
||||
import fetchItems from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/posts',
|
||||
categories: ['multimedia'],
|
||||
example: '/coomer/posts',
|
||||
parameters: {},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['coomer.su/posts', 'coomer.su/'],
|
||||
},
|
||||
],
|
||||
name: 'Recent Posts',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
url: 'coomer.su/posts',
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const currentUrl = 'posts';
|
||||
|
||||
return await fetchItems(ctx, currentUrl);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{{ if i.files }}
|
||||
{{ each i.files file }}
|
||||
{{ if file.extension === 'jpg' || file.extension === 'png' || file.extension === 'webp' || file.extension === 'jpeg' || file.extension === 'jfif' }}
|
||||
<img src="{{ file.path }}">
|
||||
{{ else if file.extension === 'm4a' || file.extension === 'mp3' || file.extension === 'ogg' }}
|
||||
<audio controls><source src="{{ file.path }}" type="audio/{{file.extention}}"></audio>
|
||||
{{ else if file.extension === 'mp4' || file.extension === 'webm' }}
|
||||
<video controls><source src="{{ file.path }}" type="video/{{file.extention}}"></video>
|
||||
{{ else }}
|
||||
<a href="{{ file.path }}">{{file.name}}</a>
|
||||
{{ /if }}
|
||||
{{ /each }}
|
||||
{{ /if }}
|
||||
|
||||
{{ if i.embed }}
|
||||
{{ if i.embed.type === 'image' }}
|
||||
<img src="{{ i.embed.thumbnail.proxy_url }}">
|
||||
{{ else if i.embed.type === 'link' }}
|
||||
{{ if i.embed.thumbnail }}
|
||||
<a href="{{ i.embed.thumbnail.url }}"><img src="{{ i.embed.thumbnail.proxy_url }}"></a>
|
||||
{{ /if }}
|
||||
<a href="{{ i.embed.url }}">{{ i.embed.title }}</a>{{ if i.embed.description }}<p>{{ i.embed.description }}</p>{{ /if }}
|
||||
{{ /if }}
|
||||
{{ /if }}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
const fetchItems = async (ctx, currentUrl) => {
|
||||
const rootUrl = 'https://coomer.su';
|
||||
currentUrl = `${rootUrl}/${currentUrl}`;
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: currentUrl,
|
||||
});
|
||||
|
||||
const $ = load(response.data);
|
||||
|
||||
let items = $('.card-list__items')
|
||||
.find('a')
|
||||
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25)
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
item = $(item);
|
||||
|
||||
return {
|
||||
link: `${rootUrl}${item.attr('href')}`,
|
||||
};
|
||||
});
|
||||
|
||||
items = await Promise.all(
|
||||
items.map((item) =>
|
||||
cache.tryGet(item.link, async () => {
|
||||
const detailResponse = await got({
|
||||
method: 'get',
|
||||
url: item.link,
|
||||
});
|
||||
|
||||
const content = load(detailResponse.data);
|
||||
|
||||
content('.ad-container').remove();
|
||||
|
||||
item.author = content('.post__user-name').text();
|
||||
item.title = content('.post__title span').first().text();
|
||||
item.pubDate = parseDate(content('.timestamp').attr('datetime'));
|
||||
item.description = content('.post__body').html();
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
title: $('title').text(),
|
||||
link: currentUrl,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
export default fetchItems;
|
||||
Loading…
Reference in New Issue