'use strict'; const bhttp = require('bhttp'); const cheerio = require('cheerio'); const moment = require('moment'); const { getPhotos, fetchProfile } = require('./gamma'); function scrape(html, site) { const $ = cheerio.load(html, { normalizeWhitespace: true }); const scenesElements = $('li[data-itemtype=scene]').toArray(); return scenesElements.reduce((accReleases, element) => { const siteName = $(element).find('.studioName a').attr('title'); if (!site.url && siteName.toLowerCase() !== site.name.toLowerCase()) { // using generic overview as fallback, scene from different site return accReleases; } const sceneLinkElement = $(element).find('.sceneTitle a'); const url = `${site.url || 'https://www.21sextury.com'}${sceneLinkElement.attr('href')}`; const title = sceneLinkElement.attr('title').trim(); const entryId = $(element).attr('data-itemid'); const date = moment .utc($(element).find('.sceneDate').text(), 'MM-DD-YYYY') .toDate(); const actors = $(element).find('.sceneActors a') .map((actorIndex, actorElement) => $(actorElement).attr('title')) .toArray(); const poster = $(element).find('.imgLink img').attr('data-original'); const trailer = `https://videothumb.gammacdn.com/307x224/${entryId}.mp4`; const [likes, dislikes] = $(element).find('.value') .toArray() .map(value => Number($(value).text())); return [ ...accReleases, { url, entryId, title, actors, date, poster, trailer: { src: trailer, }, rating: { likes, dislikes, }, site, }, ]; }, []); } async function scrapeScene(html, url, site) { const $ = cheerio.load(html, { normalizeWhitespace: true }); const sceneElement = $('#videoWrapper'); const json = $('script[type="application/ld+json"]').html(); const videoJson = $('script:contains("ScenePlayerOptions")').html(); const videoDataString = videoJson.slice(videoJson.indexOf('= {') + 2, videoJson.indexOf('};') + 1); const data = JSON.parse(json)[0]; const videoData = JSON.parse(videoDataString); const entryId = new URL(url).pathname.split('/').slice(-1)[0]; const title = videoData?.playerOptions?.sceneInfos?.sceneTitle || (data.isPartOf && data.isPartOf !== 'TBD' ? data.isPartOf.name : data.name); const dataDate = moment.utc(videoData?.playerOptions?.sceneInfos?.sceneReleaseDate, 'YYYY-MM-DD'); const date = dataDate.isValid() ? dataDate.toDate() : moment.utc(sceneElement.find('.updatedDate').text().trim(), 'MM-DD-YYYY').toDate(); const actors = data.actor.map(actor => actor.name); const description = data.description || null; // prevent empty string const likes = Number(sceneElement.find('.rating .state_1 .value').text()); const dislikes = Number(sceneElement.find('#infoWrapper .rating .state_2 .value').text()); const duration = moment.duration(data.duration.slice(2).split(':')).asSeconds(); const poster = videoData.picPreview; const trailer = `${videoData.playerOptions.host}${videoData.url}`; const photos = await getPhotos($('.picturesItem a').attr('href'), '21sextury.com', site); const tags = data.keywords.split(', '); const siteName = data.productionCompany ? data.productionCompany.name : $('#logoLink a').attr('title'); const channel = siteName && siteName.replace(/\s+/g, '').toLowerCase(); return { url, entryId, title, date, actors, description, duration, tags, poster, photos, trailer: { src: trailer, }, rating: { likes, dislikes, }, site, channel, }; } async function fetchLatest(site, page = 1) { const url = `${site.url || 'https://21sextury.com'}/en/videos/All-Categories/0/All-Pornstars/0/latest/${page}`; const res = await bhttp.get(url); return scrape(res.body.toString(), site); } async function fetchUpcoming(site) { const url = `${site.url || 'https://21sextury.com'}/en/videos/All-Categories/0/All-Pornstars/0/upcoming`; const res = await bhttp.get(url); return scrape(res.body.toString(), site); } async function fetchScene(url, site) { const res = await bhttp.get(url); return scrapeScene(res.body.toString(), url, site); } async function networkFetchProfile(actorName) { return fetchProfile(actorName, '21sextury', true); } module.exports = { fetchLatest, fetchProfile: networkFetchProfile, fetchUpcoming, fetchScene, };