158 lines
4.7 KiB
JavaScript
Executable File
158 lines
4.7 KiB
JavaScript
Executable File
'use strict';
|
|
|
|
const unprint = require('unprint');
|
|
|
|
const slugify = require('../utils/slugify');
|
|
const tryUrls = require('../utils/try-urls');
|
|
const { convert } = require('../utils/convert');
|
|
|
|
function getEntryId(url) {
|
|
return slugify(new URL(url).pathname.match(/\/scenes\/(.*?)(_vids)?.html/)[1]);
|
|
}
|
|
|
|
function scrapeAll(scenes, channel) {
|
|
return scenes.map(({ query }) => {
|
|
const release = {};
|
|
|
|
release.url = query.url('.videoPic a, h4 a');
|
|
release.entryId = getEntryId(release.url);
|
|
|
|
release.title = query.content('h4 a');
|
|
|
|
release.date = query.date('.videoInfo li:first-child ', 'MM-DD-YYYY');
|
|
release.duration = query.number('.videoInfo li:nth-child(2)') * 60 || null;
|
|
|
|
release.actors = query.all('a[href*="models/"]').map((actorEl) => ({
|
|
name: unprint.query.content(actorEl),
|
|
url: unprint.query.url(actorEl, null),
|
|
}));
|
|
|
|
release.poster = Array.from({ length: 4 }, (_value, index) => query.img('.videoPic img', { attribute: `src0_${4 - index}x`, origin: channel.origin }));
|
|
|
|
return release;
|
|
});
|
|
}
|
|
|
|
async function fetchLatest(channel, page = 1) {
|
|
const url = `${channel.url}/categories/movies_${page}.html`;
|
|
const res = await unprint.get(url, { selectAll: '.latestUpdateB' });
|
|
|
|
if (res.ok) {
|
|
return scrapeAll(res.context, channel);
|
|
}
|
|
|
|
return res.status;
|
|
}
|
|
|
|
async function fetchCaps(url) {
|
|
if (!url) {
|
|
return null;
|
|
}
|
|
|
|
const res = await unprint.get(url, { select: '.photoDetailsArea' });
|
|
|
|
if (res.ok) {
|
|
return res.context.query.imgs('.photoDPic img');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function scrapeScene({ query: pageQuery, html }, { url, entity, include }) {
|
|
const release = {};
|
|
const { query } = unprint.init(pageQuery.element('.latestUpdateBinfo'));
|
|
|
|
release.entryId = getEntryId(url);
|
|
|
|
release.title = pageQuery.content('.vidImgTitle h4');
|
|
release.description = query.content('.vidImgContent p');
|
|
|
|
release.date = query.date('.videoInfo li:first-child ', 'MM-DD-YYYY');
|
|
release.duration = query.number('.videoInfo li:nth-child(2)') * 60 || null;
|
|
|
|
release.actors = query.all('a[href*="models/"]').map((actorEl) => ({
|
|
name: unprint.query.content(actorEl),
|
|
url: unprint.query.url(actorEl, null),
|
|
}));
|
|
|
|
release.tags = query.contents('.blogTags a');
|
|
|
|
const posterPath = html.match(/useimage\s*=\s*"(.*?)"/i)?.[1];
|
|
const capsUrl = pageQuery.url('a[href*="_caps"]');
|
|
|
|
if (posterPath) {
|
|
release.poster = Array.from({ length: 4 }, (_value, index) => unprint.prefixUrl(posterPath.replace('-4x', `-${4 - index}x`), entity.url));
|
|
}
|
|
|
|
if (include.photos && capsUrl) {
|
|
release.caps = await fetchCaps(capsUrl, entity);
|
|
}
|
|
|
|
release.trailer = pageQuery.video('#download_select option[value*=".mp4"]', { attribute: 'value' });
|
|
|
|
return release;
|
|
}
|
|
|
|
function scrapeProfile({ query }, { url, actorName }) {
|
|
const profile = { url };
|
|
|
|
const bio = Object.fromEntries(query.contents('.vitalStats li').map((entry) => {
|
|
const [key, value] = entry.split(':');
|
|
|
|
if (!key || !value) {
|
|
return null;
|
|
}
|
|
|
|
return [slugify(key, '_'), value?.trim()];
|
|
}).filter(Boolean));
|
|
|
|
profile.description = `${query.content('.modelBioInfo')?.replace(new RegExp(`professional bio of ${actorName}`, 'i'), '')}${bio.awards ? ` Awards: ${bio.awards}` : ''}`;
|
|
|
|
profile.dateOfBirth = unprint.extractDate(bio.date_of_birth, 'MMMM D, YYYY');
|
|
profile.birthPlace = bio.birthplace;
|
|
profile.ethnicity = bio.ethnicity;
|
|
|
|
profile.height = unprint.extractNumber(bio.height, { match: /(\d+)\s*cm/i, matchIndex: 1 })
|
|
|| convert(bio.height?.match(/\d+\s*ft \d+\s*in/)?.[0], 'cm');
|
|
|
|
profile.weight = unprint.extractNumber(bio.weight, { match: /(\d+)\s*kg/i, matchIndex: 1 })
|
|
|| convert(bio.weight?.match(/\d+\s*lbs/)[0], 'lb', 'kg');
|
|
|
|
profile.measurements = bio.measurements;
|
|
|
|
if (/yes/i.test(bio.natural_breasts)) profile.naturalBoobs = true;
|
|
if (/no/i.test(bio.natural_breasts)) profile.naturalBoobs = false;
|
|
|
|
if (/yes/i.test(bio.tattoos)) profile.hasTattoos = true;
|
|
if (/no/i.test(bio.tattoos)) profile.hasTattoos = false;
|
|
|
|
if (/yes/i.test(bio.piercings)) profile.hasPiercings = true;
|
|
if (/no/i.test(bio.piercings)) profile.hasPiercings = false;
|
|
|
|
profile.socials = query.urls('.vitalStats a[href*="onlyfans"], .vitalStats a[href*="twitter"], .vitalStats a[href*="instagram"]');
|
|
profile.avatar = Array.from({ length: 4 }, (_value, index) => query.img('.modelBioPic img', { attribute: `src0_${4 - index}x` }));
|
|
|
|
return profile;
|
|
}
|
|
|
|
async function fetchProfile({ name: actorName, url: actorUrl }, entity) {
|
|
const { res, url } = await tryUrls([
|
|
actorUrl,
|
|
`${entity.url}/models/${slugify(actorName, '-')}.html`,
|
|
`${entity.url}/models/${slugify(actorName, '')}.html`,
|
|
`${entity.url}/models/${slugify(actorName, '_')}.html`,
|
|
]);
|
|
|
|
if (res.ok) {
|
|
return scrapeProfile(res.context, { url, entity, actorName });
|
|
}
|
|
|
|
return res.status;
|
|
}
|
|
|
|
module.exports = {
|
|
fetchLatest,
|
|
fetchProfile,
|
|
scrapeScene,
|
|
};
|