forked from DebaucheryLibrarian/traxxx
144 lines
3.4 KiB
JavaScript
Executable File
144 lines
3.4 KiB
JavaScript
Executable File
'use strict';
|
|
|
|
const unprint = require('unprint');
|
|
|
|
const { convert } = require('../utils/convert');
|
|
|
|
function scrapeScene(data, channel) {
|
|
const release = {};
|
|
|
|
release.entryId = data.slug;
|
|
release.url = `${channel.origin}/scenes/${data.slug}`;
|
|
|
|
release.title = data.title;
|
|
release.description = data.description;
|
|
|
|
release.date = unprint.extractDate(data.publish_date, 'YYYY/MM/DD HH:mm:ss');
|
|
release.duration = data.seconds_duration || unprint.extractDuration(data.videos_duration);
|
|
|
|
release.actors = (data.models_thumbs || data.models_slugs)?.map((model) => ({
|
|
name: model.name,
|
|
url: model.slug && `${channel.origin}/models/${model.slug}`,
|
|
avatar: model.thumb,
|
|
})) || data.models;
|
|
|
|
release.tags = data.tags;
|
|
release.qualities = data.videos && Object.values(data.videos).map((video) => video.height);
|
|
|
|
release.poster = [
|
|
data.trailer_screencap,
|
|
data.thumb,
|
|
data.extra_thumbnails?.[0],
|
|
].filter(Boolean);
|
|
|
|
release.photos = data.extra_thumbnails?.slice(1); // first photo is poster
|
|
release.caps = data.thumbs;
|
|
|
|
release.trailer = data.trailer_url || null; // empty string if missing
|
|
|
|
// photo count / photos duration isn't reliable, exactly 1000 for most All Anal scenes
|
|
|
|
return release;
|
|
}
|
|
|
|
async function fetchLatest(channel, page = 1) {
|
|
const url = `${channel.url}/scenes?page=${page}`;
|
|
const res = await unprint.get(url);
|
|
|
|
if (res.ok) {
|
|
if (res.context.query.exists('a[href*="stackpath.com"]')) {
|
|
throw new Error('URL blocked by StackPath');
|
|
}
|
|
|
|
const scenes = res.context.query.json('#__NEXT_DATA__')?.props.pageProps.contents.data;
|
|
|
|
if (scenes) {
|
|
return scenes.map((scene) => scrapeScene(scene, channel));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return res.status;
|
|
}
|
|
|
|
async function fetchScene(url, channel, baseRelease) {
|
|
if (baseRelease.entryId) {
|
|
// deep data identical to base data
|
|
return baseRelease;
|
|
}
|
|
|
|
const res = await unprint.get(url);
|
|
|
|
if (res.ok) {
|
|
if (res.context.query.exists('a[href*="stackpath.com"]')) {
|
|
throw new Error('URL blocked by StackPath');
|
|
}
|
|
|
|
const scene = res.context.query.json('#__NEXT_DATA__')?.props.pageProps.content;
|
|
|
|
if (scene) {
|
|
return scrapeScene(scene, channel);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return res.status;
|
|
}
|
|
|
|
async function scrapeProfile(data) {
|
|
const profile = {};
|
|
// unreliable key case, lowercase all
|
|
const bio = Object.fromEntries(Object.entries(data).map(([key, value]) => [key.toLowerCase(), value]));
|
|
|
|
profile.entryId = bio.id;
|
|
|
|
profile.gender = bio.gender;
|
|
profile.description = bio.bio;
|
|
|
|
profile.birthPlace = bio.born;
|
|
profile.dateOfBirth = unprint.extractDate(bio.birthdate, 'YYYY-MM-DD');
|
|
profile.age = bio.age; // not always in data even when displayed on site
|
|
|
|
profile.measurements = bio.measurements;
|
|
profile.height = convert(bio.height, 'cm');
|
|
profile.weight = convert(bio.weight, 'lb', 'kg');
|
|
|
|
profile.eyes = bio.eyes;
|
|
profile.hairColor = bio.hair;
|
|
|
|
profile.avatar = bio.thumb;
|
|
|
|
const tags = bio.tags?.split(',') || [];
|
|
|
|
if (tags.includes('tattoos')) profile.hasTattoos = true;
|
|
if (tags.includes('piercing')) profile.hasPiercings = true;
|
|
|
|
return profile;
|
|
}
|
|
|
|
async function fetchProfile(actor, context) {
|
|
const url = `${context.channel.url}/models/${actor.slug}`;
|
|
|
|
const res = await unprint.get(url);
|
|
|
|
if (res.ok) {
|
|
const data = res.context.query.json('#__NEXT_DATA__');
|
|
|
|
if (data.props.pageProps.model) {
|
|
return scrapeProfile(data.props.pageProps.model, context.channel);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return res.status;
|
|
}
|
|
|
|
module.exports = {
|
|
fetchLatest,
|
|
fetchProfile,
|
|
fetchScene,
|
|
};
|