Added Nubiles network.
|
@ -42,6 +42,17 @@ module.exports = {
|
|||
'wicked',
|
||||
'brazzers',
|
||||
'milehighmedia',
|
||||
[
|
||||
// Nubiles; use the same scraper, but different results per site
|
||||
'nubiles',
|
||||
'nubilesporn',
|
||||
'deeplush',
|
||||
'brattysis',
|
||||
'nfbusty',
|
||||
'anilos',
|
||||
'hotcrazymess',
|
||||
'thatsitcomshow',
|
||||
],
|
||||
'21sextury',
|
||||
'julesjordan',
|
||||
'naughtyamerica',
|
||||
|
|
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 124 KiB |
|
@ -252,6 +252,11 @@ function getTags(groupsMap) {
|
|||
alias_for: null,
|
||||
group_id: groupsMap.finish,
|
||||
},
|
||||
{
|
||||
name: 'casting',
|
||||
slug: 'casting',
|
||||
alias_for: null,
|
||||
},
|
||||
{
|
||||
name: 'cheerleader',
|
||||
slug: 'cheerleader',
|
||||
|
@ -846,6 +851,12 @@ function getTags(groupsMap) {
|
|||
alias_for: null,
|
||||
group_id: groupsMap.body,
|
||||
},
|
||||
{
|
||||
name: 'teacher',
|
||||
slug: 'teacher',
|
||||
alias_for: null,
|
||||
group_id: groupsMap.roleplay,
|
||||
},
|
||||
{
|
||||
name: 'threesome',
|
||||
slug: 'threesome',
|
||||
|
|
|
@ -184,6 +184,12 @@ const networks = [
|
|||
url: 'https://www.naughtyamerica.com',
|
||||
description: 'The best porn movies daily at Naughty America! Experience the most seductive porn stars in stunning virtual reality, 4K and HD porn videos!',
|
||||
},
|
||||
{
|
||||
slug: 'nubiles',
|
||||
name: 'Nubiles',
|
||||
url: 'https://www.nubiles.com',
|
||||
description: 'Welcome to the teen megasite that started it all! Browse our massive HD collection of fresh legal hotties at Nubiles.net.',
|
||||
},
|
||||
{
|
||||
slug: 'perfectgonzo',
|
||||
name: 'Perfect Gonzo',
|
||||
|
|
|
@ -2822,6 +2822,154 @@ const sites = [
|
|||
url: 'https://www.naughtyamerica.com/site/live-naughty-nurse',
|
||||
network: 'naughtyamerica',
|
||||
},
|
||||
// NUBILES
|
||||
{
|
||||
slug: 'anilos',
|
||||
name: 'Anilos',
|
||||
url: 'https://www.anilos.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'brattysis',
|
||||
name: 'Bratty Sis',
|
||||
url: 'https://www.brattysis.com',
|
||||
tags: ['family'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'deeplush',
|
||||
name: 'Deep Lush',
|
||||
url: 'https://www.deeplush.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'hotcrazymess',
|
||||
name: 'Hot Crazy Mess',
|
||||
url: 'https://www.hotcrazymess.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'nfbusty',
|
||||
name: 'NF Busty',
|
||||
url: 'https://www.nfbusty.com',
|
||||
tags: ['big-boobs'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'nubilefilms',
|
||||
name: 'Nubile Films',
|
||||
url: 'https://www.nubilefilms.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'nubiles',
|
||||
name: 'Nubiles',
|
||||
url: 'https://www.nubiles.net',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'nubilescasting',
|
||||
name: 'Nubiles Casting',
|
||||
url: 'https://www.nubiles-casting.com',
|
||||
tags: ['casting'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'momsteachsex',
|
||||
name: 'Moms Teach Sex',
|
||||
url: 'https://www.momsteachsex.com',
|
||||
tags: ['family', 'milf'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'petitehdporn',
|
||||
name: 'Petite HD Porn',
|
||||
url: 'https://www.petitehdporn.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'driverxxx',
|
||||
name: 'Driver XXX',
|
||||
url: 'https://www.driverxxx.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'petiteballerinasfucked',
|
||||
name: 'Petite Ballerinas Fucked',
|
||||
url: 'https://www.petiteballerinasfucked.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'teacherfucksteens',
|
||||
name: 'Teacher Fucks Teens',
|
||||
url: 'https://www.teacherfucksteens.com',
|
||||
tags: ['teacher'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'stepsiblingscaught',
|
||||
name: 'Step Siblings Caught',
|
||||
url: 'https://www.stepsiblingscaught.com',
|
||||
tags: ['family'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'princesscum',
|
||||
name: 'Princess Cum',
|
||||
url: 'https://www.princesscum.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'badteenspunished',
|
||||
name: 'Bad Teens Punished',
|
||||
url: 'https://www.badteenspunished.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'nubilesunscripted',
|
||||
name: 'Nubiles Unscripted',
|
||||
url: 'https://www.nubilesunscripted.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'bountyhunterporn',
|
||||
name: 'Bounty Hunter Porn',
|
||||
url: 'https://www.bountyhunterporn.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'daddyslilangel',
|
||||
name: 'Daddy\'s Lil Angel',
|
||||
url: 'https://www.daddyslilangel.com',
|
||||
tags: ['family', 'anal'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'myfamilypies',
|
||||
name: 'My Family Pies',
|
||||
url: 'https://www.myfamilypies.com',
|
||||
tags: ['family'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'nubileset',
|
||||
name: 'Nubiles Entertainment',
|
||||
url: 'https://www.nubileset.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'detentiongirls',
|
||||
name: 'Detention Girls',
|
||||
url: 'https://www.detentiongirls.com',
|
||||
network: 'nubiles',
|
||||
},
|
||||
{
|
||||
slug: 'thatsitcomshow',
|
||||
name: 'That Sitcom Show',
|
||||
url: 'https://www.thatsitcomshow.com',
|
||||
tags: ['parody'],
|
||||
network: 'nubiles',
|
||||
},
|
||||
// PERFECT GONZO
|
||||
{
|
||||
slug: 'allinternal',
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"no-console": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"indent": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"template-curly-spacing": "off",
|
||||
"object-curly-newline": "off",
|
||||
"max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}],
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
'use strict';
|
||||
|
||||
const { get, geta, ctxa } = require('../utils/q');
|
||||
const slugify = require('../utils/slugify');
|
||||
const { heightToCm } = require('../utils/convert');
|
||||
|
||||
const slugUrlMap = {
|
||||
nubiles: 'https://www.nubiles.net',
|
||||
nubilesporn: 'https://www.nubiles-porn.com',
|
||||
};
|
||||
|
||||
async function getPhotos(albumUrl) {
|
||||
const thumbnails = await geta(albumUrl, '.photo-thumb');
|
||||
|
||||
return thumbnails
|
||||
? thumbnails.map(({ q }) => q('source').srcset)
|
||||
: [];
|
||||
}
|
||||
|
||||
function scrapeAll(scenes, site, origin) {
|
||||
return scenes.map(({ q, qa, qu, qd }) => {
|
||||
const release = {};
|
||||
|
||||
release.title = q('.title a', true);
|
||||
|
||||
const url = qu('.title a').split('?')[0];
|
||||
const channelUrl = qu('.site-link');
|
||||
|
||||
if (/^http/.test(url)) {
|
||||
const { pathname } = new URL(url);
|
||||
release.entryId = pathname.split('/')[3];
|
||||
|
||||
if (channelUrl) release.url = `${channelUrl}${pathname}`;
|
||||
else release.url = url;
|
||||
} else {
|
||||
release.entryId = url.split('/')[3];
|
||||
|
||||
if (channelUrl) release.url = `${channelUrl}${url}`;
|
||||
else if (site?.url) release.url = `${site.url}${url}`;
|
||||
else if (origin) release.url = `${origin}${url}`;
|
||||
}
|
||||
|
||||
release.date = qd('.date', 'MMM D, YYYY');
|
||||
release.actors = qa('.models a.model', true);
|
||||
|
||||
const poster = q('img').dataset.original;
|
||||
release.poster = [
|
||||
poster.replace('_640', '_1280'),
|
||||
poster,
|
||||
];
|
||||
|
||||
release.stars = Number(q('.rating', true));
|
||||
release.likes = Number(q('.likes', true));
|
||||
|
||||
return release;
|
||||
});
|
||||
}
|
||||
|
||||
async function scrapeScene({ q, qa, qd, qp, qu, qi }, url, site) {
|
||||
const release = {};
|
||||
|
||||
const { origin, pathname } = new URL(url);
|
||||
release.url = `${origin}${pathname}`;
|
||||
|
||||
release.entryId = new URL(url).pathname.split('/')[3];
|
||||
release.title = q('.content-pane-title h2', true);
|
||||
release.description = q('.content-pane-column div', true);
|
||||
|
||||
release.date = qd('.date', 'MMM D, YYYY');
|
||||
|
||||
release.actors = qa('.content-pane-performers .model', true);
|
||||
release.tags = qa('.categories a', true);
|
||||
|
||||
release.poster = qp() || qi('.fake-video-player img');
|
||||
release.trailer = qa('source').map(source => ({
|
||||
src: source.src,
|
||||
quality: Number(source.getAttribute('res')),
|
||||
}));
|
||||
|
||||
release.stars = Number(q('.score', true));
|
||||
release.likes = Number(q('#likecount', true));
|
||||
|
||||
const albumLink = qu('.content-pane-related-links a[href*="gallery"]');
|
||||
if (albumLink) release.photos = await getPhotos(`${site.url}${albumLink}`);
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
function scrapeProfile({ q, qa, qi, qu }, _actorName, origin) {
|
||||
const profile = {};
|
||||
|
||||
const keys = qa('.model-profile h5', true);
|
||||
const values = qa('.model-profile h5 + p', true);
|
||||
|
||||
const bio = keys.reduce((acc, key, index) => ({ ...acc, [slugify(key, { delimiter: '_' })]: values[index] }), {});
|
||||
|
||||
profile.age = Number(bio.age);
|
||||
profile.description = q('.model-bio', true);
|
||||
|
||||
profile.residencePlace = bio.location;
|
||||
|
||||
profile.height = heightToCm(bio.height);
|
||||
[profile.bust, profile.waist, profile.hip] = bio.figure.split('-').map(v => Number(v) || v);
|
||||
|
||||
profile.avatar = qi('.model-profile img');
|
||||
|
||||
const releases = qa('.content-grid-item').filter(el => /video\//.test(qu(el, '.img-wrapper a'))); // filter out photos
|
||||
profile.releases = scrapeAll(ctxa(releases), null, origin);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
async function fetchLatest(site, page = 1) {
|
||||
const url = `${site.url}/video/gallery/${(page - 1) * 12}`;
|
||||
const qLatest = await geta(url, '.content-grid-item');
|
||||
|
||||
return qLatest && scrapeAll(qLatest, site);
|
||||
}
|
||||
|
||||
async function fetchScene(url, site) {
|
||||
const qScene = await get(url);
|
||||
|
||||
return qScene && scrapeScene(qScene, url, site);
|
||||
}
|
||||
|
||||
async function fetchProfile(actorName, siteSlug) {
|
||||
const firstLetter = actorName.charAt(0).toLowerCase();
|
||||
const origin = slugUrlMap[siteSlug] || `https://www.${siteSlug}.com`;
|
||||
|
||||
const url = `${origin}/model/alpha/${firstLetter}`;
|
||||
const { qa } = await get(url);
|
||||
|
||||
const modelPath = qa('.content-grid-item a.title').find(el => slugify(el.textContent) === slugify(actorName));
|
||||
|
||||
if (modelPath) {
|
||||
const modelUrl = `${origin}${modelPath}`;
|
||||
const qModel = await get(modelUrl);
|
||||
|
||||
if (qModel) return scrapeProfile(qModel, actorName, origin);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchLatest,
|
||||
fetchScene,
|
||||
fetchProfile,
|
||||
};
|
|
@ -33,6 +33,7 @@ const mindgeek = require('./mindgeek');
|
|||
const mofos = require('./mofos');
|
||||
const naturals = require('./21naturals');
|
||||
const naughtyamerica = require('./naughtyamerica');
|
||||
const nubiles = require('./nubiles');
|
||||
const perfectgonzo = require('./perfectgonzo');
|
||||
const pervcity = require('./pervcity');
|
||||
const pornhub = require('./pornhub');
|
||||
|
@ -65,13 +66,12 @@ module.exports = {
|
|||
blowpass,
|
||||
brazzers,
|
||||
ddfnetwork,
|
||||
metrohd,
|
||||
digitalplayground,
|
||||
dogfart,
|
||||
dogfartnetwork: dogfart,
|
||||
famedigital,
|
||||
evilangel,
|
||||
fakehub,
|
||||
famedigital,
|
||||
fantasymassage,
|
||||
girlsway,
|
||||
insex,
|
||||
|
@ -81,16 +81,18 @@ module.exports = {
|
|||
kink,
|
||||
legalporno,
|
||||
men,
|
||||
metrohd,
|
||||
mikeadriano,
|
||||
milehighmedia,
|
||||
mindgeek,
|
||||
mofos,
|
||||
naughtyamerica,
|
||||
nubiles,
|
||||
perfectgonzo,
|
||||
pervcity,
|
||||
pornpros,
|
||||
private: privateNetwork,
|
||||
puretaboo,
|
||||
naughtyamerica,
|
||||
realitykings,
|
||||
score,
|
||||
teamskeet,
|
||||
|
@ -102,20 +104,23 @@ module.exports = {
|
|||
xempire,
|
||||
},
|
||||
actors: {
|
||||
// ordered by data priority
|
||||
'21sextury': sextury,
|
||||
anilos: nubiles,
|
||||
babes,
|
||||
bangbros,
|
||||
blowpass,
|
||||
boobpedia,
|
||||
brattysis: nubiles,
|
||||
brazzers,
|
||||
ddfnetwork,
|
||||
deeplush: nubiles,
|
||||
digitalplayground,
|
||||
famedigital,
|
||||
evilangel,
|
||||
fakehub,
|
||||
famedigital,
|
||||
freeones,
|
||||
freeonesLegacy,
|
||||
hotcrazymess: nubiles,
|
||||
iconmale,
|
||||
julesjordan,
|
||||
kellymadison,
|
||||
|
@ -125,9 +130,14 @@ module.exports = {
|
|||
milehighmedia,
|
||||
mofos,
|
||||
naughtyamerica,
|
||||
nfbusty: nubiles,
|
||||
nubilefilms: nubiles,
|
||||
nubiles,
|
||||
nubilesporn: nubiles,
|
||||
pornhub,
|
||||
realitykings,
|
||||
score,
|
||||
thatsitcomshow: nubiles,
|
||||
transangels,
|
||||
twistys,
|
||||
wicked,
|
||||
|
|
|
@ -1,105 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
/* eslint-disable newline-per-chained-call */
|
||||
const bhttp = require('bhttp');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const moment = require('moment');
|
||||
const { get, geta } = require('../utils/q');
|
||||
|
||||
const { matchTags } = require('../tags');
|
||||
function scrapeLatest(scenes, site) {
|
||||
return scenes.map(({ q, qa, qu, qd }) => {
|
||||
const release = {};
|
||||
|
||||
function scrapeLatest(html, site) {
|
||||
const { document } = new JSDOM(html).window;
|
||||
const sceneElements = $('.scenes-latest').toArray();
|
||||
release.title = q('.title a', true);
|
||||
|
||||
return sceneElements.map((element) => {
|
||||
const actors = $(element).find('.actors a').map((actorIndex, actorElement) => $(actorElement).text()).toArray();
|
||||
const pathname = qu('.title a');
|
||||
release.entryId = pathname.split('/')[3];
|
||||
release.url = `${site.url}${pathname}`;
|
||||
|
||||
return {
|
||||
url,
|
||||
entryId,
|
||||
title,
|
||||
actors,
|
||||
date,
|
||||
rating: {
|
||||
likes,
|
||||
dislikes,
|
||||
stars,
|
||||
},
|
||||
site,
|
||||
};
|
||||
release.date = qd('.date', 'MMM DD, YYYY');
|
||||
release.actors = qa('.models a.model', true);
|
||||
|
||||
release.poster = q('img').dataset.original;
|
||||
|
||||
release.stars = Number(q('.rating', true));
|
||||
release.likes = Number(q('.likes', true));
|
||||
|
||||
console.log(release);
|
||||
return release;
|
||||
});
|
||||
}
|
||||
|
||||
function scrapeUpcoming(html, site) {
|
||||
const { document } = new JSDOM(html).window;
|
||||
const sceneElements = $('.scenes-upcoming').toArray();
|
||||
function scrapeScene(({ q }), _site) {
|
||||
const release = {};
|
||||
|
||||
return sceneElements.map((element) => {
|
||||
const actors = $(element).find('.actors a').map((actorIndex, actorElement) => $(actorElement).text()).toArray();
|
||||
|
||||
return {
|
||||
url,
|
||||
entryId,
|
||||
title,
|
||||
actors,
|
||||
date,
|
||||
rating: {
|
||||
likes,
|
||||
dislikes,
|
||||
stars,
|
||||
},
|
||||
site,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function scrapeScene(html, url, site) {
|
||||
const $ = cheerio.load(html, { normalizeWhitespace: true });
|
||||
|
||||
const actors = $('.actors a').map((actorIndex, actorElement) => $(actorElement).text()).toArray();
|
||||
|
||||
const rawTags = $('.tags a').map((tagIndex, tagElement) => $(tagElement).text()).toArray();
|
||||
const tags = await matchTags(rawTags);
|
||||
|
||||
return {
|
||||
url,
|
||||
entryId,
|
||||
title,
|
||||
description,
|
||||
actors,
|
||||
director,
|
||||
date,
|
||||
duration,
|
||||
tags,
|
||||
rating: {
|
||||
likes,
|
||||
dislikes,
|
||||
stars,
|
||||
},
|
||||
site,
|
||||
};
|
||||
console.log(release);
|
||||
return release;
|
||||
}
|
||||
|
||||
async function fetchLatest(site, page = 1) {
|
||||
const res = await bhttp.get(`${site.url}/url`);
|
||||
const url = `${site.url}/${page}`;
|
||||
const qLatest = await geta(url, '.selector');
|
||||
|
||||
return scrapeLatest(res.body.toString(), site);
|
||||
}
|
||||
|
||||
async function fetchUpcoming(site) {
|
||||
const res = await bhttp.get(`${site.url}/url`);
|
||||
|
||||
return scrapeUpcoming(res.body.toString(), site);
|
||||
return qLatest && scrapeLatest(qLatest, site);
|
||||
}
|
||||
|
||||
async function fetchScene(url, site) {
|
||||
const res = await bhttp.get(url);
|
||||
const qScene = await get(url);
|
||||
|
||||
return scrapeScene(res.body.toString(), url, site);
|
||||
return qScene && scrapeScene(qScene, site);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchLatest,
|
||||
fetchUpcoming,
|
||||
fetchScene,
|
||||
};
|
||||
|
|
|
@ -191,6 +191,10 @@ function init(element, window) {
|
|||
}
|
||||
|
||||
function initAll(context, selector, window) {
|
||||
if (Array.isArray(context)) {
|
||||
return context.map(element => init(element, window));
|
||||
}
|
||||
|
||||
return Array.from(context.querySelectorAll(selector))
|
||||
.map(element => init(element, window));
|
||||
}
|
||||
|
|