'use strict'; const mime = require('mime'); const qu = require('../utils/qu'); const http = require('../utils/http'); const slugify = require('../utils/slugify'); const { convert } = require('../utils/convert'); function scrapeSceneX(scene) { const release = {}; release.entryId = scene.id; release.url = `https://tour.topwebmodels.com/scenes/${scene.id}/${slugify(scene.title, '-', { removePunctuation: true })}`; release.title = scene.title; release.description = scene.description; release.duration = qu.durationToSeconds(scene.videos_duration); release.date = new Date(scene.release_date); release.actors = scene.models .map((actor) => (/&/.test(actor.name) ? actor.name.split(/\s*&\s*/) : { name: actor.name, gender: actor.gender || null, avatar: actor.thumb, url: `https://tour.topwebmodels.com/models/${actor.id}/${slugify(actor.name)}`, })) .flat(); release.stars = scene.rating; release.tags = scene.tags.map((tag) => tag.name); if (mime.getType(scene.thumb) === 'image/gif') { release.teaser = scene.thumb; } else { release.poster = scene.thumb; } release.channel = slugify(scene.sites[0]?.name, ''); return release; } function scrapeAll(scenes) { return scenes.map(scrapeSceneX); } async function scrapeProfile(actor, options) { const profile = {}; profile.dateOfBirth = /1969-12-31/.test(actor.attributes.birthdate.value) ? null : qu.extractDate(actor.attributes.birthdate.value, 'YYYY-MM-DD'); // ignore epoch profile.age = actor.attributes.age.value === 51 ? null : actor.attributes.age.value; // ignore epoch profile.gender = actor.attributes.gender || null; profile.height = convert(actor.attributes.height.value, 'cm'); profile.weight = convert(actor.attributes.weight.value, 'lb', 'kg'); const [bust, cup, waist, hip] = actor.attributes.measurements.value?.match(/(\d+)(\w+)-(\d+)-(\d+)/)?.slice(1) || []; profile.bust = Number(bust); profile.cup = cup; profile.waist = Number(waist); profile.hip = Number(hip); profile.ethnicity = actor.attributes.ethnicity.value; profile.birthPlace = actor.attributes.born.value; profile.eyes = actor.attributes.eyes.value; profile.hairColor = actor.attributes.hair.value.split('/')[0]; profile.url = `https://tour.topwebmodels.com/models/${actor.id}/${slugify(actor.name, '-', { removePunctuation: true })}`; profile.avatar = actor.thumb; if (options.includeActorScenes) { const res = await http.get(profile.url, { extract: { runScripts: 'dangerously' } }); if (res.ok) { profile.scenes = scrapeAll(res.window.__DATA__?.data?.videos?.items); } } return profile; } async function fetchLatest(channel, page) { const res = await http.get(`https://tour.topwebmodels.com/api/sites/${channel.parameters?.slug || channel.slug}?page=${page}`, { headers: { Referer: 'https://tour.topwebmodels.com', 'api-key': channel.parameters?.apiKey || channel.parent?.parameters?.apiKey, 'x-Requested-With': 'XMLHttpRequest', }, }); if (res.ok) { return scrapeAll(res.body.videos.items); } return res.status; } async function fetchScene(url) { const res = await http.get(url, { extract: { runScripts: 'dangerously' } }); if (res.ok) { return { ...scrapeSceneX(res.window.__DATA__.data.video), ...(/\.gif/.test(res.window.__DATA__.data.video.thumb) && { teaser: res.window.__DATA__.data.video.thumb }), poster: res.window.__DATA__.data.file_poster, }; } return res.status; } async function fetchProfile(baseActor, entity, options) { const searchRes = await http.get(`https://tour.topwebmodels.com/api/search-preview/${baseActor.name}`, { headers: { Referer: 'https://tour.topwebmodels.com', 'api-key': entity.parameters?.apiKey, 'x-Requested-With': 'XMLHttpRequest', }, }); if (!searchRes.ok) { return searchRes.status; } const actor = searchRes.body.models.items.find((model) => slugify(model.name) === slugify(baseActor.name)); if (actor) { return scrapeProfile(actor, options); } return null; } module.exports = { fetchLatest, fetchScene, fetchProfile, };