diff --git a/src/scrapers/bamvisions.js b/src/scrapers/bamvisions.js index 3e0d250e..4479f62c 100755 --- a/src/scrapers/bamvisions.js +++ b/src/scrapers/bamvisions.js @@ -1,87 +1,112 @@ 'use strict'; -const format = require('template-format'); +const unprint = require('unprint'); -const { get, geta, initAll, formatDate } = require('../utils/qu'); const slugify = require('../utils/slugify'); +const tryUrls = require('../utils/try-urls'); -const { feetInchesToCm } = require('../utils/convert'); +const { convert } = require('../utils/convert'); function scrapeAll(scenes, site) { - return scenes.map(({ qu }) => { + return scenes.map(({ query }) => { const release = {}; - release.title = qu.q('h3 a', true); - release.url = qu.url('h3 a'); + release.url = query.url('h3 a'); - release.date = qu.date('.item-meta li', 'MMMM D, YYYY', /\w+ \d{1,2}, \d{4}/); - release.duration = qu.dur('.item-meta li:nth-child(2)'); - release.description = qu.q('.description', true); + release.title = query.content('h3 a'); + release.description = query.content('.description'); - release.actors = qu.all('a[href*="/models"]', true); - if (/bts/i.test(release.title)) release.tags = ['behind the scenes']; + release.date = query.date('.item-meta li', 'MMMM D, YYYY'); + release.duration = query.duration('.item-meta li:nth-child(2)'); - [release.poster, ...release.photos] = qu.all('.item-thumbs img') + release.actors = query.all('a[href*="/models"]').map((actorEl) => ({ + name: unprint.query.content(actorEl), + url: unprint.query.url(actorEl, null), + })); + + [release.poster, ...release.photos] = query.all('.item-thumbs img') .map((source) => [ source.getAttribute('src0_3x'), source.getAttribute('src0_2x'), source.getAttribute('src0_1x'), ] .filter(Boolean) - .map((fallback) => (/^http/.test(fallback) ? fallback : `${site.url}${fallback}`))); + .map((fallback) => unprint.prefixUrl(fallback, site.url))); - release.entryId = `${formatDate(release.date, 'YYYY-MM-DD')}-${slugify(release.title)}`; + release.entryId = `${unprint.formatDate(release.date, 'YYYY-MM-DD')}-${slugify(release.title)}`; return release; }); } -function scrapeScene({ html, qu }, url, site) { - const release = { url }; +async function fetchLatest(site, page = 1) { + const url = `${site.url}/categories/movies/${page}/latest/`; + const res = await unprint.get(url, { selectAll: '.item-episode' }); - release.title = qu.q('.item-episode h4 a', true); - release.date = qu.date('.item-meta li', 'MMMM D, YYYY', /\w+ \d{1,2}, \d{4}/); - release.duration = qu.dur('.item-meta li:nth-child(2)'); - release.description = qu.q('.description', true); + if (res.ok) { + return scrapeAll(res.context, site); + } - release.actors = qu.all('.item-episode a[href*="/models"]', true); - if (/bts/i.test(release.title)) release.tags = ['behind the scenes']; + return res.status; +} + +function scrapeScene({ html, query }, site) { + const release = {}; + + release.title = query.content('.item-episode h4 a'); + release.description = query.content('.description'); + + release.date = query.date('.item-meta li', 'MMMM D, YYYY'); + release.duration = query.duration('.item-meta li:nth-child(2)'); + + release.actors = query.all('.item-episode a[href*="/models"]').map((actorEl) => ({ + name: unprint.query.content(actorEl), + url: unprint.query.url(actorEl, null), + })); const posterPath = html.match(/poster="(.*.jpg)"/)?.[1]; const trailerPath = html.match(/video src="(.*.mp4)"/)?.[1]; if (posterPath) { - const poster = /^http/.test(posterPath) ? posterPath : `${site.url}${posterPath}`; release.poster = [ - poster.replace('-1x', '-3x'), - poster.replace('-1x', '-2x'), - poster, - ]; + posterPath.replace('-1x', '-3x'), + posterPath.replace('-1x', '-2x'), + posterPath, + ].map((poster) => unprint.prefixUrl(poster, site.url)); } if (trailerPath) { - const trailer = /^http/.test(trailerPath) ? trailerPath : `${site.url}${trailerPath}`; - release.trailer = { src: trailer }; + release.trailer = /^http/.test(trailerPath) + ? trailerPath + : `${site.url}${trailerPath}`; } - release.entryId = `${formatDate(release.date, 'YYYY-MM-DD')}-${slugify(release.title)}`; + release.photoCount = query.number('.item-meta li:last-child'); + release.entryId = `${unprint.formatDate(release.date, 'YYYY-MM-DD')}-${slugify(release.title)}`; return release; } +async function fetchScene(url, site) { + const res = await unprint.get(url); + + if (res.ok) { + return scrapeScene(res.context, site); + } + + return res.status; +} + async function fetchActorReleases(actorId, site, page = 1, accScenes = []) { - const url = site.parameters?.sets - ? `${site.parameters.sets}?id=${actorId}&page=${page}` - : `${site.url}/sets.php?id=${actorId}&page=${page}`; + const url = `${site.url}/sets.php?id=${actorId}&page=${page}`; + const res = await unprint.get(url, { selectAll: '.item-episode' }); - const res = await get(url); + if (!res.ok) { + return []; + } - if (!res.ok) return []; - - const quReleases = initAll(res.item.el, '.item-episode'); - const releases = scrapeAll(quReleases, site); - - const nextPage = res.item.qu.q(`a[href*="page=${page + 1}"]`); + const releases = scrapeAll(res.context, site); + const nextPage = res.context.query.url(`a[href*="page=${page + 1}"]`); if (nextPage) { return fetchActorReleases(actorId, site, page + 1, accScenes.concat(releases)); @@ -90,36 +115,30 @@ async function fetchActorReleases(actorId, site, page = 1, accScenes = []) { return accScenes.concat(releases); } -async function scrapeProfile({ qu }, site, withScenes) { - if (!qu.exists('.content')) { +async function scrapeProfile({ query }, url, site, withScenes) { + if (!query.exists('.content')) { // page probably returned a 404 with a 200 HTTP code return null; } - const profile = {}; + const profile = { url }; - const bio = qu.all('.stats li', true).reduce((acc, row) => { - const [key, value] = row.split(':'); - return { ...acc, [slugify(key, '_')]: value.trim() }; - }, {}); + const bio = Object.fromEntries(query.all('.stats li').map((bioEl) => [ + slugify(unprint.query.content(bioEl, 'strong'), '_'), + unprint.query.text(bioEl), + ])); - if (bio.height) profile.height = feetInchesToCm(bio.height); - if (bio.measurements) { - const [bust, waist, hip] = bio.measurements.split('-'); - - if (bust) profile.bust = bust; - if (waist) profile.waist = Number(waist); - if (hip) profile.hip = Number(hip); - } + profile.height = convert(bio.height, 'cm'); + profile.measurements = bio.measurements; profile.avatar = [ - qu.q('.profile-pic img', 'src0_3x'), - qu.q('.profile-pic img', 'src0_2x'), - qu.q('.profile-pic img', 'src0_1x'), + query.img('.profile-pic img', { attribute: 'src0_3x' }), + query.img('.profile-pic img', { attribute: 'src0_2x' }), + query.img('.profile-pic img', { attribute: 'src0_1x' }), ].filter(Boolean).map((source) => (/^http/.test(source) ? source : `${site.url}${source}`)); if (withScenes) { - const actorId = qu.q('.profile-pic img', 'id')?.match(/set-target-(\d+)/)?.[1]; + const actorId = query.attribute('.profile-pic img', 'id')?.match(/set-target-(\d+)/)?.[1]; if (actorId) { profile.releases = await fetchActorReleases(actorId, site); @@ -129,37 +148,18 @@ async function scrapeProfile({ qu }, site, withScenes) { return profile; } -async function fetchLatest(site, page = 1) { - const url = site.parameters?.latest - ? format(site.parameters.latest, { page }) - : `${site.url}/categories/movies/${page}/latest/`; - const res = await geta(url, '.item-episode'); +async function fetchProfile({ name: actorName, url: actorUrl }, { channel }, include) { + const { res, url } = await tryUrls([ + actorUrl, + `${channel.url}/models/${slugify(actorName, '-')}.html`, + `${channel.url}/models/${slugify(actorName, '')}.html`, + ]); - return res.ok ? scrapeAll(res.items, site) : res.status; -} + if (res.ok) { + return scrapeProfile(res.context, url, channel, include.scenes); + } -async function fetchScene(url, site) { - const res = await get(url); - - return res.ok ? scrapeScene(res.item, url, site) : res.status; -} - -async function fetchProfile({ name: actorName }, { site }, include) { - const actorSlugA = slugify(actorName, ''); - const actorSlugB = slugify(actorName); - - const urlA = site.parameters?.profile - ? format(site.parameters.profile, { slug: actorSlugA }) - : `${site.url}/models/${actorSlugA}.html`; - - const urlB = site.parameters?.profile - ? format(site.parameters.profile, { slug: actorSlugB }) - : `${site.url}/models/${actorSlugB}.html`; - - const resA = await get(urlA); - const res = resA.ok ? resA : await get(urlB); - - return res.ok ? scrapeProfile(res.item, site, include.scenes) : res.status; + return res.status; } module.exports = { diff --git a/tests/profiles.js b/tests/profiles.js index 161e6684..79adb275 100644 --- a/tests/profiles.js +++ b/tests/profiles.js @@ -115,6 +115,7 @@ const actors = [ // perv city { entity: 'pervcity', name: 'Brooklyn Gray', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'eyes', 'hairColor'] }, { entity: 'dpdiva', name: 'Liz Jordan', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'eyes', 'hairColor'] }, + { entity: 'bamvisions', name: 'Abella Danger', fields: ['avatar', 'height', 'measurements'] }, // radical { entity: 'bjraw', name: 'Nikki Knightly', fields: ['avatar', 'description', 'gender', 'dateOfBirth', 'birthPlace', 'measurements', 'height', 'weight', 'eyes', 'hairColor'] }, { entity: 'gotfilled', name: 'Alexa Chains', fields: ['avatar', 'description', 'gender', 'dateOfBirth', 'birthPlace', 'measurements', 'height', 'weight', 'eyes', 'hairColor'] }, @@ -164,6 +165,9 @@ const actors = [ { entity: '2poles1hole', name: 'Bambi Blitz', url: 'https://2poles1hole.com/model/425', fields: ['avatar', 'entryId', 'description', 'gender', 'age', 'dateOfBirth', 'measurements', 'height', 'foot', 'hairColor', 'eyeColor'] }, { entity: 'creampiled', name: 'Nicole Aria', url: 'https://creampiled.com/model/616', fields: ['avatar', 'entryId', 'description', 'gender', 'age', 'dateOfBirth', 'measurements', 'height', 'foot', 'hairColor', 'eyeColor'] }, { entity: 'popuporgies', name: 'Nicole Aria', url: 'https://popuporgies.com/model/616', fields: ['avatar', 'entryId', 'description', 'gender', 'age', 'dateOfBirth', 'measurements', 'height', 'foot', 'hairColor', 'eyeColor'] }, + // missax + { entity: 'missax', name: 'Alexis Fawx', fields: ['avatar', 'description'] }, + { entity: 'allherluv', name: 'Krissy Lynn', fields: ['avatar', 'description'] }, // etc. { entity: 'analvids', name: 'Veronica Leal', fields: ['avatar', 'gender', 'birthCountry', 'nationality', 'age', 'aliases', 'nationality'] }, { entity: 'archangel', name: 'Summer Brielle', fields: ['avatar', 'description', 'dateOfBirth', 'age', 'measurements', 'height', 'aliases'] },