'use strict'; /* eslint-disable newline-per-chained-call */ const bhttp = require('bhttp'); const cheerio = require('cheerio'); const { JSDOM } = require('jsdom'); const moment = require('moment'); const { heightToCm, lbsToKg } = require('../utils/convert'); const hairMap = { Blonde: 'blonde', Brunette: 'brown', 'Black Hair': 'black', Redhead: 'red', }; function scrape(html, site, upcoming) { const $ = cheerio.load(html, { normalizeWhitespace: true }); const sceneElements = $('.release-card.scene').toArray(); return sceneElements.reduce((acc, element) => { const isUpcoming = $(element).find('.icon-upcoming.active').length === 1; if ((upcoming && !isUpcoming) || (!upcoming && isUpcoming)) { return acc; } const sceneLinkElement = $(element).find('a'); const url = `https://www.brazzers.com${sceneLinkElement.attr('href')}`; const title = sceneLinkElement.attr('title'); const entryId = url.split('/').slice(-3, -2)[0]; const date = moment.utc($(element).find('time').text(), 'MMMM DD, YYYY').toDate(); const actors = $(element).find('.model-names a').map((actorIndex, actorElement) => $(actorElement).attr('title')).toArray(); const likes = Number($(element).find('.label-rating .like-amount').text()); const dislikes = Number($(element).find('.label-rating .dislike-amount').text()); const poster = `https:${$(element).find('.card-main-img').attr('data-src')}`; const photos = $(element).find('.card-overlay .image-under').map((photoIndex, photoElement) => `https:${$(photoElement).attr('data-src')}`).toArray(); return acc.concat({ url, entryId, title, actors, date, poster, photos, rating: { likes, dislikes, }, site, }); }, []); } async function scrapeScene(html, url, site) { const $ = cheerio.load(html, { normalizeWhitespace: true }); const videoJson = $('script:contains("window.videoUiOptions")').html(); const videoData = JSON.parse(videoJson.slice(videoJson.indexOf('{"stream_info":'), videoJson.lastIndexOf('"},') + 2)); const entryId = url.split('/').slice(-3, -2)[0]; const title = $('.scene-title[itemprop="name"]').text(); const description = $('#scene-description p[itemprop="description"]') .contents() .first() .text() .trim(); const date = moment.utc($('.more-scene-info .scene-date').text(), 'MMMM DD, YYYY').toDate(); const actors = $('.related-model a').map((actorIndex, actorElement) => $(actorElement).text()).toArray(); const duration = Number($('.scene-length[itemprop="duration"]').attr('content').slice(1, -1)) * 60; const likes = Number($('.label-rating .like').text()); const dislikes = Number($('.label-rating .dislike').text()); const siteElement = $('.niche-site-logo'); // const siteUrl = `https://www.brazzers.com${siteElement.attr('href').slice(0, -1)}`; const siteName = siteElement.attr('title'); const channel = siteName.replace(/\s+/g, '').toLowerCase(); const tags = $('.tag-card-container a').map((tagIndex, tagElement) => $(tagElement).text()).toArray(); const poster = `https:${videoData.poster}`; const photos = $('.carousel-thumb a').map((photoIndex, photoElement) => `https:${$(photoElement).attr('href')}`).toArray(); const trailer = Object.entries(videoData.stream_info.http.paths).map(([quality, path]) => ({ src: `https:${path}`, quality: Number(quality.match(/\d{3,}/)[0]), })); return { url, entryId, title, description, actors, date, poster, photos, trailer, duration, rating: { likes, dislikes, }, tags, site, channel, }; } function scrapeActorSearch(html, url, actorName) { const { document } = new JSDOM(html).window; const actorLink = document.querySelector(`a[title="${actorName}" i]`); return actorLink ? actorLink.href : null; } function scrapeProfile(html, url, actorName) { const { document } = new JSDOM(html).window; const avatarEl = document.querySelector('.big-pic-model-container img'); const descriptionEl = document.querySelector('.model-profile-specs p'); const bioKeys = Array.from(document.querySelectorAll('.profile-spec-list label'), el => el.textContent.replace(/\n+|\s{2,}/g, '').trim()); const bioValues = Array.from(document.querySelectorAll('.profile-spec-list var'), el => el.textContent.replace(/\n+|\s{2,}/g, '').trim()); const bio = bioKeys.reduce((acc, key, index) => ({ ...acc, [key]: bioValues[index] }), {}); const profile = { name: actorName, }; if (bio.Ethnicity) profile.ethnicity = bio.Ethnicity; if (bio.Measurements && bio.Measurements.match(/\d+[A-Z]+-\d+-\d+/)) [profile.bust, profile.waist, profile.hip] = bio.Measurements.split('-'); if (bio['Date of Birth'] && bio['Date of Birth'] !== 'Unknown') profile.birthdate = moment.utc(bio['Date of Birth'], 'MMMM DD, YYYY').toDate(); if (bio['Birth Location']) profile.birthPlace = bio['Birth Location']; if (bio['Pussy Type']) profile.pussy = bio['Pussy Type'].split(',').slice(-1)[0].toLowerCase(); if (bio.Height) profile.height = heightToCm(bio.Height); if (bio.Weight) profile.weight = lbsToKg(bio.Weight.match(/\d+/)[0]); if (bio['Hair Color']) profile.hair = hairMap[bio['Hair Color']] || bio['Hair Color'].toLowerCase(); if (bio['Tits Type'] && bio['Tits Type'].match('Natural')) profile.naturalBoobs = true; if (bio['Tits Type'] && bio['Tits Type'].match('Enhanced')) profile.naturalBoobs = false; if (bio['Body Art'] && bio['Body Art'].match('Tattoo')) profile.hasTattoos = true; if (bio['Body Art'] && bio['Body Art'].match('Piercing')) profile.hasPiercings = true; if (descriptionEl) profile.description = descriptionEl.textContent.trim(); if (avatarEl) profile.avatar = `https:${avatarEl.src}`; profile.releases = Array.from(document.querySelectorAll('.release-card-container .scene-card-title a'), el => `https://brazzers.com${el.href}`); return profile; } async function fetchLatest(site, page = 1) { const res = await bhttp.get(`${site.url}/page/${page}/`); return scrape(res.body.toString(), site, false); } async function fetchUpcoming(site) { const res = await bhttp.get(`${site.url}/`); return scrape(res.body.toString(), site, true); } async function fetchScene(url, site) { const res = await bhttp.get(url); return scrapeScene(res.body.toString(), url, site); } async function fetchProfile(actorName) { const searchUrl = 'https://brazzers.com/pornstars-search/'; const searchRes = await bhttp.get(searchUrl, { headers: { Cookie: `textSearch=${encodeURIComponent(actorName)};`, }, }); const actorLink = scrapeActorSearch(searchRes.body.toString(), searchUrl, actorName); if (actorLink) { const url = `https://brazzers.com${actorLink}`; const res = await bhttp.get(url); return scrapeProfile(res.body.toString(), url, actorName); } return null; } module.exports = { fetchLatest, fetchProfile, fetchScene, fetchUpcoming, };