traxxx/src/scrapers/wankzvr.js

178 lines
4.6 KiB
JavaScript

'use strict';
const qu = require('../utils/qu');
const http = require('../utils/http');
const slugify = require('../utils/slugify');
async function getTrailerUrl(release, channel, request) {
const csrfToken = request.cookie.match('csrfst=(.*?);')?.[1];
if (!csrfToken) {
return null;
}
const res = await http.post(`${channel.url}/ajax/player-config.json`, {
item_id: release.entryId,
}, {
headers: {
'X-CSRF-Token': csrfToken,
},
session: request.session,
encodeJSON: false,
});
if (res.ok) {
const trailers = res.body.streams.map((trailer) => ({
src: trailer.url,
quality: Number(trailer.id?.match(/\d+/)?.[0] || trailer?.name.match(/\d+/)?.[0]),
vr: true,
}));
return {
trailers,
poster: qu.prefixUrl(res.body.poster, res.body.thumbCDN),
};
}
return null;
}
function scrapeAll(scenes, channel) {
return scenes.map(({ query }) => {
const release = {};
release.url = query.url('a', 'href', { origin: channel.url });
release.entryId = new URL(release.url).pathname.match(/(\d+)\/?$/)?.[1];
release.title = query.cnt('.card__h');
release.date = query.date('.card__date', 'D MMMM, YYYY');
release.actors = query.all('.card__links a').map((el) => ({
name: qu.query.cnt(el),
url: qu.query.url(el, null, 'href', { origin: channel.url }),
}));
const poster = query.srcset('picture source[type="image/jpeg"]', 'data-srcset')
|| query.srcset('picture source[type="image/jpeg"]', 'srcset')
|| query.srcset('.video__cover', 'srcset');
if (poster?.[0]) {
release.poster = [
poster[0].replace(/small|tiny/, 'large'),
...poster,
];
release.teaser = poster[0].replace(/\b(cover|hero|\d+)\/[a-z0-9_]+\.[a-z]+$/i, 'roll.webm'); // actually how site generates teaser URL
}
release.channel = channel.slug; // avoid being assigned to WankzVR network
return release;
});
}
async function scrapeScene({ query }, url, channel, baseRelease, options, request) {
const release = {};
release.entryId = new URL(url).pathname.match(/(\d+)\/?$/)?.[1];
release.title = query.cnt('.detail__title');
release.description = query.cnt('.detail__txt');
release.date = query.date('.detail__date', 'D MMMM, YYYY');
release.duration = query.number('.time') * 60;
release.actors = (query.all('.detail__header-lg .detail__models a') || query.all('.detail__header-sm .detail__models a')).map((el) => ({
name: qu.query.cnt(el),
url: qu.query.url(el, null, 'href', { origin: channel.url }),
}));
release.tags = query.cnts('.tag-list .tag').concat(query.cnts('.detail__specs-list .detail__specs-item'));
release.photos = query.all('.photo-strip__slide').map((el) => ([
qu.query.img(el, null, 'data-src'),
qu.query.img(el, 'img', 'src'),
]));
if (options.includePosters || options.includeTrailers) {
const { trailers, poster } = await getTrailerUrl(release, channel, request);
release.trailer = trailers;
release.poster = poster;
}
return release;
}
async function fetchActorScenes({ query }, url, entity, page = 1, accScenes = []) {
const scenes = scrapeAll(qu.initAll(query.all('.cards-list .card')), entity);
const hasNextPage = !query.exists('.pagenav__link.inactive');
if (hasNextPage) {
const { origin, pathname, searchParams } = new URL(url);
searchParams.set('p', page + 1);
const res = await qu.get(`${origin}${pathname}?${searchParams}`);
if (res.ok) {
return fetchActorScenes(res.item, url, entity, page + 1, accScenes.concat(scenes));
}
}
return accScenes.concat(scenes);
}
async function scrapeProfile({ query }, url, entity, options) {
const profile = {};
const bio = query.all('.person__meta__item').reduce((acc, el) => ({
...acc,
[slugify(qu.query.cnt(el, '.person__meta__label'))]: qu.query.text(el),
}), {});
profile.description = query.cnt('.person__content');
profile.gender = entity.slug === 'tranzvr' ? 'transsexual' : 'female';
profile.age = bio.age;
profile.birthPlace = bio.birthplace;
profile.height = parseInt(bio.height, 10);
profile.measurements = bio.measurements;
profile.avatar = query.srcset('.person__avatar img');
if (options.includeActorScenes) {
profile.scenes = await fetchActorScenes({ query }, url, entity);
}
return profile;
}
async function fetchLatest(channel, page) {
const res = await qu.getAll(`${channel.url}/videos?o=d&p=${page}`, '.cards-list .card');
if (res.ok) {
return scrapeAll(res.items, channel);
}
return res.status;
}
async function fetchProfile(baseActor, { entity }, options) {
const url = `${entity.url}/${baseActor.slug}`;
const res = await qu.get(url);
if (res.ok) {
return scrapeProfile(res.item, url, entity, options);
}
return res.status;
}
module.exports = {
fetchLatest,
scrapeScene,
fetchProfile,
};