From 94bf2073973d6cf0146470c6a0ff68f9e8f54b9b Mon Sep 17 00:00:00 2001 From: Niels Simenon Date: Sat, 1 Feb 2020 01:15:40 +0100 Subject: [PATCH] Added Wicked network. Merged Evil Angel, XEmpire and Wicked into generic Gamma scraper. --- assets/js/releases/actions.js | 2 - public/img/logos/wicked/favicon.png | Bin 0 -> 1043 bytes public/img/logos/wicked/network.png | Bin 0 -> 20735 bytes public/img/logos/wicked/wicked.png | Bin 0 -> 20735 bytes seeds/00_networks.js | 6 + seeds/01_sites.js | 11 +- seeds/03_tags.js | 5 + src/actors.js | 2 +- src/argv.js | 16 ++ src/releases.js | 4 +- src/scrape-releases.js | 4 + src/scrape-sites.js | 10 +- src/scrapers/evilangel.js | 233 +------------------ src/scrapers/gamma.js | 348 ++++++++++++++++++++++++---- src/scrapers/scrapers.js | 3 + src/scrapers/wicked.js | 10 + src/scrapers/xempire.js | 162 +------------ 17 files changed, 385 insertions(+), 431 deletions(-) create mode 100644 public/img/logos/wicked/favicon.png create mode 100644 public/img/logos/wicked/network.png create mode 100644 public/img/logos/wicked/wicked.png create mode 100644 src/scrapers/wicked.js diff --git a/assets/js/releases/actions.js b/assets/js/releases/actions.js index e26c2efe6..7c97decc7 100644 --- a/assets/js/releases/actions.js +++ b/assets/js/releases/actions.js @@ -4,8 +4,6 @@ import { curateRelease } from '../curate'; function initReleasesActions(store, _router) { async function fetchReleases({ _commit }, { limit = 100 }) { - console.log(store.state.ui.filter, store.getters.after, store.getters.before); - const { releases } = await graphql(` query Releases( $limit:Int = 1000, diff --git a/public/img/logos/wicked/favicon.png b/public/img/logos/wicked/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3abc516d6bb8f36be1b05cb1ced3a68e7185195d GIT binary patch literal 1043 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

ME3Hst+>CjoeEk41_foZrpAW{QoBGH2 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 UD { try { - const profile = await scraper.fetchProfile(actorEntry ? actorEntry.name : actorName); + const profile = await scraper.fetchProfile(actorEntry ? actorEntry.name : actorName, scraperSlug); return { ...profile, diff --git a/src/argv.js b/src/argv.js index b6981767c..ae6085431 100644 --- a/src/argv.js +++ b/src/argv.js @@ -35,6 +35,12 @@ const { argv } = yargs alias: 'with-scenes', default: false, }) + .option('with-profiles', { + describe: 'Scrape profiles for new actors after fetching scenes', + type: 'boolean', + alias: 'with-actors', + default: true, + }) .option('scene', { describe: 'Scrape scene info from URL', type: 'array', @@ -55,6 +61,16 @@ const { argv } = yargs type: 'boolean', default: true, }) + .option('latest', { + describe: 'Scrape latest releases if available', + type: 'boolean', + default: true, + }) + .option('upcoming', { + describe: 'Scrape upcoming releases if available', + type: 'boolean', + default: true, + }) .option('redownload', { describe: 'Don\'t ignore duplicates, update existing entries', type: 'boolean', diff --git a/src/releases.js b/src/releases.js index e465f89b5..2e66bc176 100644 --- a/src/releases.js +++ b/src/releases.js @@ -425,7 +425,9 @@ async function storeReleases(releases) { storeReleaseAssets(storedReleases), ]); - await scrapeBasicActors(); + if (argv.withProfiles) { + await scrapeBasicActors(); + } return { releases: storedReleases, diff --git a/src/scrape-releases.js b/src/scrape-releases.js index f8954d76e..78d022c55 100644 --- a/src/scrape-releases.js +++ b/src/scrape-releases.js @@ -65,8 +65,12 @@ async function scrapeRelease(source, basicRelease = null, type = 'scene') { : await scraper.fetchMovie(url, site, release); return { + url, ...scrapedRelease, ...release, + ...(scrapedRelease && release?.tags && { + tags: release.tags.concat(scrapedRelease.tags), + }), site, }; } diff --git a/src/scrape-sites.js b/src/scrape-sites.js index 512a8035b..2f181d684 100644 --- a/src/scrape-sites.js +++ b/src/scrape-sites.js @@ -30,6 +30,10 @@ async function findDuplicateReleaseIds(latestReleases, accReleases) { } async function scrapeUniqueReleases(scraper, site, afterDate = getAfterDate(), accReleases = [], page = 1) { + if (!argv.latest || !scraper.fetchLatest) { + return []; + } + const latestReleases = await scraper.fetchLatest(site, page); if (latestReleases.length === 0) { @@ -58,7 +62,7 @@ async function scrapeUniqueReleases(scraper, site, afterDate = getAfterDate(), a } async function scrapeUpcomingReleases(scraper, site) { - if (scraper.fetchUpcoming) { + if (argv.upcoming && scraper.fetchUpcoming) { const upcomingReleases = await scraper.fetchUpcoming(site); return upcomingReleases.map(release => ({ ...release, upcoming: true })); @@ -100,7 +104,9 @@ async function scrapeSiteReleases(scraper, site) { scrapeUpcomingReleases(scraper, site), // fetch basic release info from upcoming overview ]); - logger.info(`${site.name}: Found ${newReleases.length} recent releases, ${upcomingReleases.length} upcoming releases`); + if (argv.upcoming) { + logger.info(`${site.name}: ${argv.latest ? 'Found' : 'Ignoring'} ${newReleases.length || ''}latest releases, ${argv.upcoming ? '' : 'ignoring '}${upcomingReleases.length || ''} upcoming releases`); + } const baseReleases = [...newReleases, ...upcomingReleases]; diff --git a/src/scrapers/evilangel.js b/src/scrapers/evilangel.js index e58794b36..c31fa6e80 100644 --- a/src/scrapers/evilangel.js +++ b/src/scrapers/evilangel.js @@ -1,235 +1,10 @@ 'use strict'; -const bhttp = require('bhttp'); -const cheerio = require('cheerio'); -const moment = require('moment'); - -const { getPhotos } = require('./gamma'); - -async function scrape(json, site) { - return Promise.all(json.map(async (scene) => { - const { - title, - description, - length, - master_categories: tags, - ratings_up: likes, - ratings_down: dislikes, - } = scene; - - const entryId = scene.clip_id; - const url = `https://evilangel.com/en/video/${scene.url_title}/${entryId}`; - const date = moment(scene.release_date, 'YYYY-MM-DD').toDate(); - const actors = scene.actors.map(({ name }) => name); - const director = scene.directors[0].name; - - const poster = `https://images-evilangel.gammacdn.com/movies${scene.pictures.resized}`; - const movie = `https://evilangel.com/en/movie/${scene.url_movie_title}/${scene.movie_id}`; - - return { - url, - entryId, - title, - description, - length, - actors, - director, - date, - tags, - poster, - rating: { - likes, - dislikes, - }, - movie, - site, - }; - })); -} - -async function scrapeScene(html, url, site) { - const $ = cheerio.load(html, { normalizeWhitespace: true }); - const json = $('script[type="application/ld+json"]').html(); - const videoJson = $('script:contains("window.ScenePlayerOptions")').html(); - - const [data, data2] = JSON.parse(json); - const videoData = JSON.parse(videoJson.slice(videoJson.indexOf('{'), videoJson.indexOf('};') + 1)); - const entryId = new URL(url).pathname.split('/').slice(-1)[0]; - - const { - name: title, - description, - } = data; - // date in data object is not the release date of the scene, but the date the entry was added - const date = moment.utc($('.updatedDate').first().text(), 'MM-DD-YYYY').toDate(); - - const actors = data.actor.map(actor => actor.name); - const hasTrans = data.actor.some(actor => actor.gender === 'shemale'); - - const director = (data.director && data.director[0].name) || (data2.director && data2.director[0].name) || null; - const stars = (data.aggregateRating.ratingValue / data.aggregateRating.bestRating) * 5; - - const duration = moment.duration(data.duration.slice(2).split(':')).asSeconds(); - - const rawTags = data.keywords.split(', '); - const tags = hasTrans ? [...rawTags, 'transsexual'] : rawTags; - - const poster = videoData.picPreview; - const trailer = `${videoData.playerOptions.host}${videoData.url}`; - - const photos = await getPhotos($('.picturesItem a').attr('href'), 'evilangel.com', site); - - return { - url, - entryId, - title, - date, - actors, - director, - description, - duration, - tags, - poster, - photos, - trailer: { - src: trailer, - quality: parseInt(videoData.sizeOnLoad, 10), - }, - rating: { - stars, - }, - site, - }; -} - -function scrapeActor(data, releases) { - const actor = {}; - - if (data.male === 1) actor.gender = 'male'; - if (data.female === 1) actor.gender = 'female'; - if (data.shemale === 1 || data.trans === 1) actor.gender = 'transsexual'; - - if (data.description) actor.description = data.description.trim(); - - if (data.attributes.ethnicity) actor.ethnicity = data.attributes.ethnicity; - if (data.attributes.eye_color) actor.eyes = data.attributes.eye_color; - if (data.attributes.hair_color) actor.hair = data.attributes.hair_color; - - const avatarPath = Object.values(data.pictures).reverse()[0]; - actor.avatar = `https://images01-evilangel.gammacdn.com/actors${avatarPath}`; - - actor.releases = releases.map(release => `https://evilangel.com/en/video/${release.url_title}/${release.clip_id}`); - - return actor; -} - -async function fetchApiCredentials() { - const res = await bhttp.get('https://evilangel.com/en/videos'); - const body = res.body.toString(); - - const apiLine = body.split('\n').find(bodyLine => bodyLine.match('apiKey')); - const apiSerial = apiLine.slice(apiLine.indexOf('{'), apiLine.indexOf('};') + 1); - const apiData = JSON.parse(apiSerial); - - const { applicationID: appId, apiKey } = apiData.api.algolia; - const userAgent = 'Algolia for vanilla JavaScript (lite) 3.27.0;instantsearch.js 2.7.4;JS Helper 2.26.0'; - - const apiUrl = `https://${appId.toLowerCase()}-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=${userAgent}&x-algolia-application-id=${appId}&x-algolia-api-key=${apiKey}`; - - return { - appId, - apiKey, - userAgent, - apiUrl, - }; -} - -async function fetchLatest(site, page = 1, upcoming = false) { - const { apiUrl } = await fetchApiCredentials(); - - const res = await bhttp.post(apiUrl, { - requests: [ - { - indexName: 'all_scenes', - params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=${page - 1}&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["upcoming:${upcoming ? 1 : 0}"]]`, - }, - ], - }, { - headers: { - Referer: 'https://www.evilangel.com/en/videos', - }, - encodeJSON: true, - }); - - return scrape(res.body.results[0].hits, site); -} - -async function fetchUpcoming(site) { - return fetchLatest(site, 1, true); -} - -async function fetchScene(url, site) { - const res = await bhttp.get(url); - - return scrapeScene(res.body.toString(), url, site); -} - -async function fetchActorScenes(actorName, apiUrl) { - const res = await bhttp.post(apiUrl, { - requests: [ - { - indexName: 'all_scenes', - params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=0&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["actors.name:${actorName}"]]`, - }, - ], - }, { - headers: { - Referer: 'https://www.evilangel.com/en/videos', - }, - encodeJSON: true, - }); - - if (res.statusCode === 200 && res.body.results[0].hits.length > 0) { - return res.body.results[0].hits; - } - - return []; -} - -async function fetchProfile(actorName) { - const { apiUrl } = await fetchApiCredentials(); - const actorSlug = encodeURI(actorName); - - const res = await bhttp.post(apiUrl, { - requests: [ - { - indexName: 'all_actors', - params: `query=${actorSlug}`, - }, - ], - }, { - headers: { - Referer: `https://www.evilangel.com/en/search?query=${actorSlug}&tab=actors`, - }, - encodeJSON: true, - }); - - if (res.statusCode === 200 && res.body.results[0].hits.length > 0) { - const actorData = res.body.results[0].hits.find(actor => actor.name === actorName); - - if (actorData) { - const actorScenes = await fetchActorScenes(actorName, apiUrl); - - return scrapeActor(actorData, actorScenes); - } - } - - return null; -} +const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma'); module.exports = { - fetchLatest, - fetchProfile, + fetchLatest: fetchApiLatest, + fetchProfile: fetchApiProfile, fetchScene, - fetchUpcoming, + fetchUpcoming: fetchApiUpcoming, }; diff --git a/src/scrapers/gamma.js b/src/scrapers/gamma.js index f1de3b113..33243b3c5 100644 --- a/src/scrapers/gamma.js +++ b/src/scrapers/gamma.js @@ -4,6 +4,7 @@ const Promise = require('bluebird'); const bhttp = require('bhttp'); const { JSDOM } = require('jsdom'); const cheerio = require('cheerio'); +const moment = require('moment'); async function fetchPhotos(url) { const res = await bhttp.get(url); @@ -39,33 +40,191 @@ function scrapePhotos(html) { }); } -async function getPhotos(albumPath, siteDomain) { - const albumUrl = `https://${siteDomain}${albumPath}`; +async function getPhotos(albumPath, site) { + const albumUrl = `${site.url}${albumPath}`; try { const html = await fetchPhotos(albumUrl); const $ = cheerio.load(html, { normalizeWhitespace: true }); const photos = scrapePhotos(html); - const pages = $('.paginatorPages a').map((pageIndex, pageElement) => $(pageElement).attr('href')).toArray(); + const lastPage = $('.Gamma_Paginator a.last').attr('href')?.match(/\d+$/)[0]; - const otherPhotos = await Promise.map(pages, async (page) => { - const pageUrl = `https://${siteDomain}${page}`; - const pageHtml = await fetchPhotos(pageUrl); + if (lastPage) { + const otherPages = Array.from({ length: Number(lastPage) }, (_value, index) => index + 1).slice(1); - return scrapePhotos(pageHtml); - }, { - concurrency: 2, - }); + const otherPhotos = await Promise.map(otherPages, async (page) => { + const pageUrl = `${site.url}/${albumPath}/${page}`; + const pageHtml = await fetchPhotos(pageUrl); - return photos.concat(otherPhotos.flat()); + return scrapePhotos(pageHtml); + }, { + concurrency: 2, + }); + + return photos.concat(otherPhotos.flat()); + } + + return photos; } catch (error) { - console.error(`Failed to fetch ${siteDomain} photos from ${albumPath}: ${error.message}`); + console.error(`Failed to fetch ${site.name} photos from ${albumUrl}: ${error.message}`); return []; } } +async function scrapeApiReleases(json, site) { + return json.map((scene) => { + const release = { + entryId: scene.clip_id, + title: scene.title, + description: scene.description, + duration: scene.length, + likes: scene.ratings_up, + dislikes: scene.ratings_down, + }; + + release.url = `${site.url}/en/video/${scene.url_title}/${release.entryId}`; + release.date = moment.utc(scene.release_date, 'YYYY-MM-DD').toDate(); + release.actors = scene.actors.map(({ name }) => name); + release.director = scene.directors[0].name; + + release.tags = scene.master_categories.concat(scene.categories?.map(category => category.name)); + + const posterPath = scene.pictures.resized || (scene.pictures.nsfw?.top && Object.values(scene.pictures.nsfw.top)[0]); + + if (posterPath) { + release.poster = [ + `https://images-evilangel.gammacdn.com/movies${posterPath}`, + `https://transform.gammacdn.com/movies${posterPath}`, + ]; + } + + release.movie = `${site.url}/en/movie/${scene.url_movie_title}/${scene.movie_id}`; + + return release; + }); +} + +function scrapeAll(html, site) { + const $ = cheerio.load(html, { normalizeWhitespace: true }); + const scenesElements = $('li[data-itemtype=scene]').toArray(); + + return scenesElements.map((element) => { + const sceneLinkElement = $(element).find('.sceneTitle a'); + + const url = `${site.url}${sceneLinkElement.attr('href')}`; + const title = sceneLinkElement.attr('title'); + + 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 [likes, dislikes] = $(element).find('.value') + .toArray() + .map(value => Number($(value).text())); + + const poster = $(element).find('.imgLink img').attr('data-original'); + const trailer = `https://videothumb.gammacdn.com/307x224/${entryId}.mp4`; + + return { + url, + entryId, + title, + actors, + director: 'Mason', + date, + poster, + trailer: { + src: trailer, + quality: 224, + }, + rating: { + likes, + dislikes, + }, + site, + }; + }); +} + +async function scrapeScene(html, url, site) { + const $ = cheerio.load(html, { normalizeWhitespace: true }); + const release = { $ }; + + const json = $('script[type="application/ld+json"]').html(); + const videoJson = $('script:contains("window.ScenePlayerOptions")').html(); + + const [data, data2] = JSON.parse(json); + const videoData = JSON.parse(videoJson.slice(videoJson.indexOf('{'), videoJson.indexOf('};') + 1)); + + [release.entryId] = new URL(url).pathname.split('/').slice(-1); + + release.title = data.name; + release.description = data.description; + + // date in data object is not the release date of the scene, but the date the entry was added + const dateString = $('.updatedDate').first().text().trim(); + const dateMatch = dateString.match(/\d{2,4}-\d{2}-\d{2,4}/)?.[0]; + release.date = moment.utc(dateMatch, ['MM-DD-YYYY', 'YYYY-MM-DD']).toDate(); + + release.director = data.director?.[0].name || data2?.director?.[0].name; + release.actors = data.actor.map(actor => actor.name); + const hasTrans = data.actor.some(actor => actor.gender === 'shemale'); + + const stars = (data.aggregateRating.ratingValue / data.aggregateRating.bestRating) * 5; + if (stars) release.rating = { stars }; + + release.duration = moment.duration(data.duration.slice(2).split(':')).asSeconds(); + + const rawTags = data.keywords?.split(', '); + release.tags = hasTrans ? [...rawTags, 'transsexual'] : rawTags; + + release.poster = videoData.picPreview; + release.photos = await getPhotos($('.picturesItem a').attr('href'), site); + + const trailer = `${videoData.playerOptions.host}${videoData.url}`; + release.trailer = [ + { + src: trailer.replace('hd', 'sm'), + quality: 240, + }, + { + src: trailer.replace('hd', 'med'), + quality: 360, + }, + { + src: trailer.replace('hd', 'big'), + quality: 480, + }, + { + // probably 540p + src: trailer, + quality: parseInt(videoData.sizeOnLoad, 10), + }, + { + src: trailer.replace('hd', '720p'), + quality: 720, + }, + { + src: trailer.replace('hd', '1080p'), + quality: 1080, + }, + { + src: trailer.replace('hd', '4k'), + quality: 2160, + }, + ]; + + return release; +} + function scrapeActorSearch(html, url, actorName) { const { document } = new JSDOM(html).window; const actorLink = document.querySelector(`a[title="${actorName}" i]`); @@ -112,6 +271,113 @@ function scrapeProfile(html, url, actorName, siteSlug) { return profile; } +function scrapeApiProfile(data, releases, siteSlug) { + const profile = {}; + + if (data.male === 1) profile.gender = 'male'; + if (data.female === 1) profile.gender = 'female'; + if (data.shemale === 1 || data.trans === 1) profile.gender = 'transsexual'; + + if (data.description) profile.description = data.description.trim(); + + if (data.attributes.ethnicity) profile.ethnicity = data.attributes.ethnicity; + if (data.attributes.eye_color) profile.eyes = data.attributes.eye_color; + if (data.attributes.hair_color) profile.hair = data.attributes.hair_color; + + const avatarPath = Object.values(data.pictures).reverse()[0]; + if (avatarPath) profile.avatar = `https://images01-evilangel.gammacdn.com/actors${avatarPath}`; + + profile.releases = releases.map(release => `https://${siteSlug}.com/en/video/${release.url_title}/${release.clip_id}`); + + return profile; +} + +async function fetchApiCredentials(referer) { + const res = await bhttp.get(referer); + const body = res.body.toString(); + + const apiLine = body.split('\n').find(bodyLine => bodyLine.match('apiKey')); + const apiSerial = apiLine.slice(apiLine.indexOf('{'), apiLine.indexOf('};') + 1); + const apiData = JSON.parse(apiSerial); + + const { applicationID: appId, apiKey } = apiData.api.algolia; + const userAgent = 'Algolia for vanilla JavaScript (lite) 3.27.0;instantsearch.js 2.7.4;JS Helper 2.26.0'; + + const apiUrl = `https://${appId.toLowerCase()}-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=${userAgent}&x-algolia-application-id=${appId}&x-algolia-api-key=${apiKey}`; + + return { + appId, + apiKey, + userAgent, + apiUrl, + }; +} + +async function fetchApiLatest(site, page = 1, upcoming = false) { + const referer = `${site.url}/en/videos`; + const { apiUrl } = await fetchApiCredentials(referer); + + const res = await bhttp.post(apiUrl, { + requests: [ + { + indexName: 'all_scenes', + params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=${page - 1}&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["upcoming:${upcoming ? 1 : 0}"]]`, + }, + ], + }, { + headers: { + Referer: referer, + }, + encodeJSON: true, + }); + + return scrapeApiReleases(res.body.results[0].hits, site); +} + +async function fetchApiUpcoming(site) { + return fetchApiLatest(site, 1, true); +} + +async function fetchLatest(site, page = 1) { + const res = await bhttp.get(`${site.url}/en/videos/AllCategories/0/${page}`); + + return scrapeAll(res.body.toString(), site); +} + +async function fetchUpcoming(site) { + const res = await bhttp.get(`${site.url}/en/videos/AllCategories/0/1/upcoming`); + + return scrapeAll(res.body.toString(), site); +} + +async function fetchScene(url, site) { + const res = await bhttp.get(url); + + return scrapeScene(res.body.toString(), url, site); +} + +async function fetchActorScenes(actorName, apiUrl, siteSlug) { + const res = await bhttp.post(apiUrl, { + requests: [ + { + indexName: 'all_scenes', + params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=0&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["actors.name:${actorName}"]]`, + }, + ], + }, { + headers: { + Referer: `https://www.${siteSlug}.com/en/videos`, + }, + encodeJSON: true, + }); + + if (res.statusCode === 200 && res.body.results[0].hits.length > 0) { + return res.body.results[0].hits; + } + + return []; +} + async function fetchProfile(actorName, siteSlug, altSearchUrl) { const actorSlug = actorName.toLowerCase().replace(/\s+/, '+'); const searchUrl = altSearchUrl @@ -139,38 +405,17 @@ async function fetchProfile(actorName, siteSlug, altSearchUrl) { return null; } -async function fetchApiCredentials(referer) { - const res = await bhttp.get(referer); - const body = res.body.toString(); +async function fetchApiProfile(actorName, siteSlug) { + const actorSlug = encodeURI(actorName); + const referer = `https://www.${siteSlug}.com/en/search?query=${actorSlug}&tab=actors`; - const apiLine = body.split('\n').find(bodyLine => bodyLine.match('apiKey')); - const apiSerial = apiLine.slice(apiLine.indexOf('{'), apiLine.indexOf('};') + 1); - const apiData = JSON.parse(apiSerial); - - const { applicationID: appId, apiKey } = apiData.api.algolia; - const userAgent = 'Algolia for vanilla JavaScript (lite) 3.27.0;instantsearch.js 2.7.4;JS Helper 2.26.0'; - - const apiUrl = `https://${appId.toLowerCase()}-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=${userAgent}&x-algolia-application-id=${appId}&x-algolia-api-key=${apiKey}`; - - return { - appId, - apiKey, - userAgent, - apiUrl, - }; -} - -async function fetchLatest(site, page = 1, upcoming = false) { - const referer = `${site.url}/en/videos`; const { apiUrl } = await fetchApiCredentials(referer); - console.log(referer); - const res = await bhttp.post(apiUrl, { requests: [ { - indexName: 'all_scenes', - params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=${page - 1}&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["upcoming:${upcoming ? 1 : 0}"]]`, + indexName: 'all_actors', + params: `query=${actorSlug}`, }, ], }, { @@ -180,14 +425,31 @@ async function fetchLatest(site, page = 1, upcoming = false) { encodeJSON: true, }); - console.log(res.body.results); + if (res.statusCode === 200 && res.body.results[0].hits.length > 0) { + const actorData = res.body.results[0].hits.find(actor => actor.name === actorName); - // return scrape(res.body.results[0].hits, site); + if (actorData) { + const actorScenes = await fetchActorScenes(actorName, apiUrl, siteSlug); + + return scrapeApiProfile(actorData, actorScenes, siteSlug); + } + } + + return null; } module.exports = { - getPhotos, - fetchProfile, - scrapeProfile, + fetchApiLatest, + fetchApiProfile, + fetchApiUpcoming, fetchLatest, + fetchProfile, + fetchScene, + fetchUpcoming, + getPhotos, + scrapeApiProfile, + scrapeApiReleases, + scrapeProfile, + scrapeAll, + scrapeScene, }; diff --git a/src/scrapers/scrapers.js b/src/scrapers/scrapers.js index 024ccccb4..ecee79667 100644 --- a/src/scrapers/scrapers.js +++ b/src/scrapers/scrapers.js @@ -36,6 +36,7 @@ const mofos = require('./mofos'); const naughtyamerica = require('./naughtyamerica'); const twentyonesextury = require('./21sextury'); const xempire = require('./xempire'); +const wicked = require('./wicked'); // profiles const boobpedia = require('./boobpedia'); @@ -80,12 +81,14 @@ module.exports = { teamskeet, vixen, vogov, + wicked, xempire, }, actors: { // ordered by data priority '21sextury': twentyonesextury, evilangel, + wicked, mofos, realitykings, digitalplayground, diff --git a/src/scrapers/wicked.js b/src/scrapers/wicked.js new file mode 100644 index 000000000..c31fa6e80 --- /dev/null +++ b/src/scrapers/wicked.js @@ -0,0 +1,10 @@ +'use strict'; + +const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma'); + +module.exports = { + fetchLatest: fetchApiLatest, + fetchProfile: fetchApiProfile, + fetchScene, + fetchUpcoming: fetchApiUpcoming, +}; diff --git a/src/scrapers/xempire.js b/src/scrapers/xempire.js index 94bf56901..355dcea8d 100644 --- a/src/scrapers/xempire.js +++ b/src/scrapers/xempire.js @@ -1,168 +1,26 @@ '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.map((element) => { - const sceneLinkElement = $(element).find('.sceneTitle a'); - - const url = `${site.url}${sceneLinkElement.attr('href')}`; - const title = sceneLinkElement.attr('title'); - - 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 [likes, dislikes] = $(element).find('.value') - .toArray() - .map(value => Number($(value).text())); - - const poster = $(element).find('.imgLink img').attr('data-original'); - const trailer = `https://videothumb.gammacdn.com/307x224/${entryId}.mp4`; - - return { - url, - entryId, - title, - actors, - director: 'Mason', - date, - poster, - trailer: { - src: trailer, - quality: 224, - }, - rating: { - likes, - dislikes, - }, - site, - }; - }); -} - -async function scrapeScene(html, url, site) { - const $ = cheerio.load(html, { normalizeWhitespace: true }); - const json = $('script[type="application/ld+json"]').html(); - const json2 = $('script:contains("dataLayer = ")').html(); - const videoJson = $('script:contains("window.ScenePlayerOptions")').html(); - - const data = JSON.parse(json)[0]; - const data2 = JSON.parse(json2.slice(json2.indexOf('[{'), -1))[0]; - const videoData = JSON.parse(videoJson.slice(videoJson.indexOf('{"id":'), videoJson.indexOf('};') + 1)); - - const entryId = data2.sceneDetails.sceneId || new URL(url).pathname.split('/').slice(-1)[0]; - - const title = data2.sceneDetails.sceneTitle || $('meta[name="twitter:title"]').attr('content'); - const description = data2.sceneDetails.sceneDescription || data.description || $('meta[name="twitter:description"]').attr('content'); - // date in data object is not the release date of the scene, but the date the entry was added - const date = moment.utc($('.updatedDate').first().text(), 'MM-DD-YYYY').toDate(); - - const actors = (data2.sceneDetails.sceneActors || data.actor).map(actor => actor.actorName || actor.name); - const stars = (data.aggregateRating.ratingValue / data.aggregateRating.bestRating) * 5; - - const duration = moment.duration(data.duration.slice(2).split(':')).asSeconds(); - - const siteDomain = $('meta[name="twitter:domain"]').attr('content') || 'allblackx.com'; // only AllBlackX has no twitter domain, no other useful hints available - const siteSlug = siteDomain && siteDomain.split('.')[0].toLowerCase(); - const siteUrl = siteDomain && `https://www.${siteDomain}`; - - const poster = videoData.picPreview; - const trailer = `${videoData.playerOptions.host}${videoData.url}`; - - const photos = await getPhotos($('.picturesItem a').attr('href'), siteDomain, site); - - const tags = data.keywords.split(', '); - - return { - url: `${siteUrl}/en/video/${new URL(url).pathname.split('/').slice(-2).join('/')}`, - entryId, - title, - date, - actors, - director: 'Mason', - description, - duration, - poster, - photos, - trailer: [ - { - src: trailer.replace('hd', 'sm'), - quality: 240, - }, - { - src: trailer.replace('hd', 'med'), - quality: 360, - }, - { - src: trailer.replace('hd', 'big'), - quality: 480, - }, - { - // probably 540p - src: trailer, - quality: parseInt(videoData.sizeOnLoad, 10), - }, - { - src: trailer.replace('hd', '720p'), - quality: 720, - }, - { - src: trailer.replace('hd', '1080p'), - quality: 1080, - }, - { - src: trailer.replace('hd', '4k'), - quality: 2160, - }, - ], - tags, - rating: { - stars, - }, - site, - channel: siteSlug, - }; -} - -async function fetchLatest(site, page = 1) { - const res = await bhttp.get(`${site.url}/en/videos/AllCategories/0/${page}`); - - return scrape(res.body.toString(), site); -} - -async function fetchUpcoming(site) { - const res = await bhttp.get(`${site.url}/en/videos/AllCategories/0/1/upcoming`); - - return scrape(res.body.toString(), site); -} +const { fetchLatest, fetchUpcoming, scrapeScene, fetchProfile } = require('./gamma'); async function fetchScene(url, site) { const res = await bhttp.get(url); - return scrapeScene(res.body.toString(), url, site); -} + const release = await scrapeScene(res.body.toString(), url, site); -async function xEmpireFetchProfile(actorName) { - return fetchProfile(actorName, 'xempire'); + const siteDomain = release.$('meta[name="twitter:domain"]').attr('content') || 'allblackx.com'; // only AllBlackX has no twitter domain, no other useful hints available + const siteSlug = siteDomain && siteDomain.split('.')[0].toLowerCase(); + // const siteUrl = siteDomain && `https://www.${siteDomain}`; + + release.channel = siteSlug; + + return release; } module.exports = { fetchLatest, - fetchProfile: xEmpireFetchProfile, + fetchProfile, fetchUpcoming, fetchScene, };