'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 { get, ex } = require('../utils/q'); const slugify = require('../utils/slugify'); const { heightToCm, lbsToKg } = require('../utils/convert'); const hairMap = { Blonde: 'blonde', Brunette: 'brown', 'Black Hair': 'black', Redhead: 'red', }; function scrapeAll(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(); const channel = slugify($(element).find('.collection').attr('title'), ''); return acc.concat({ url, entryId, title, actors, date, poster, photos, rating: { likes, dislikes, }, channel, site, }); }, []); } async function scrapeScene(html, url, _site) { const $ = cheerio.load(html, { normalizeWhitespace: true }); const release = {}; const videoJson = $('script:contains("window.videoUiOptions")').html(); const videoString = videoJson.slice(videoJson.indexOf('{"stream_info":'), videoJson.lastIndexOf('},') + 1); const videoData = JSON.parse(videoString); [release.entryId] = url.split('/').slice(-3, -2); release.title = $('.scene-title[itemprop="name"]').text(); release.description = $('#scene-description p[itemprop="description"]') .contents() .first() .text() .trim(); release.date = moment.utc($('.more-scene-info .scene-date').text(), 'MMMM DD, YYYY').toDate(); release.duration = Number($('.scene-length[itemprop="duration"]').attr('content').slice(1, -1)) * 60; const actorsFromCards = $('.featured-model .card-image a').map((actorIndex, actorElement) => { const avatar = `https:${$(actorElement).find('img').attr('data-src')}`; return { name: $(actorElement).attr('title'), avatar: [avatar.replace('medium.jpg', 'large.jpg'), avatar], }; }).toArray(); release.actors = actorsFromCards || $('.related-model a').map((actorIndex, actorElement) => $(actorElement).text()).toArray(); release.likes = Number($('.label-rating .like').text()); release.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'); release.channel = siteName.replace(/\s+/g, '').toLowerCase(); release.tags = $('.tag-card-container a').map((tagIndex, tagElement) => $(tagElement).text()).toArray(); release.photos = $('.carousel-thumb a').map((photoIndex, photoElement) => `https:${$(photoElement).attr('href')}`).toArray(); const posterPath = videoData?.poster || $('meta[itemprop="thumbnailUrl"]').attr('content') || $('#trailer-player-container').attr('data-player-img'); if (posterPath) release.poster = `https:${posterPath}`; if (videoData) { release.trailer = Object.entries(videoData.stream_info.http.paths).map(([quality, path]) => ({ src: `https:${path}`, quality: Number(quality.match(/\d{3,}/)[0]), })); } return release; } function scrapeActorSearch(html, url, actorName) { const { document } = new JSDOM(html).window; const actorLink = document.querySelector(`a[title="${actorName}" i]`); return actorLink ? actorLink.href : null; } async function fetchActorReleases({ qu, html }, accReleases = []) { const releases = scrapeAll(html); const next = qu.url('.pagination .next a'); if (next) { const url = `https://www.brazzers.com${next}`; const res = await get(url); if (res.ok) { return fetchActorReleases(res.item, accReleases.concat(releases)); } } return accReleases.concat(releases); } async function scrapeProfile(html, url, actorName) { const qProfile = ex(html); const { q, qa } = qProfile; const bioKeys = qa('.profile-spec-list label', true).map(key => key.replace(/\n+|\s{2,}/g, '').trim()); const bioValues = qa('.profile-spec-list var', true).map(value => value.replace(/\n+|\s{2,}/g, '').trim()); const bio = bioKeys.reduce((acc, key, index) => ({ ...acc, [key]: bioValues[index] }), {}); const profile = { name: actorName, }; profile.description = q('.model-profile-specs p', true); 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; const avatarEl = q('.big-pic-model-container img'); if (avatarEl) profile.avatar = `https:${avatarEl.src}`; profile.releases = await fetchActorReleases(qProfile); return profile; } async function fetchLatest(site, page = 1) { const res = await bhttp.get(`${site.url}/page/${page}/`); return scrapeAll(res.body.toString(), site, false); } async function fetchUpcoming(site) { const res = await bhttp.get(`${site.url}/`); return scrapeAll(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, };