'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, };