Added Aziani. Added URL origin parameter to relevant qu methods.
|
@ -38,7 +38,10 @@
|
|||
:class="{ expanded }"
|
||||
/>
|
||||
|
||||
<div class="networks">
|
||||
<div
|
||||
v-if="networks.length > 0"
|
||||
class="networks"
|
||||
>
|
||||
<Network
|
||||
v-for="childNetwork in networks"
|
||||
:key="`network-${childNetwork.id}`"
|
||||
|
|
|
@ -25,6 +25,11 @@ module.exports = {
|
|||
'speculumplays',
|
||||
'creampiereality',
|
||||
]],
|
||||
['aziani', [
|
||||
'amberathome',
|
||||
'marycarey',
|
||||
'racqueldevonshire',
|
||||
]],
|
||||
['blowpass', ['sunlustxxx']],
|
||||
['ddfnetwork', [
|
||||
'fuckinhd',
|
||||
|
@ -134,6 +139,9 @@ module.exports = {
|
|||
'ddfnetwork',
|
||||
'bangbros',
|
||||
'kellymadison',
|
||||
'gangbangcreampie',
|
||||
'gloryholesecrets',
|
||||
'aziani',
|
||||
'legalporno',
|
||||
'score',
|
||||
'boobpedia',
|
||||
|
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 832 B |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 508 KiB |
After Width: | Height: | Size: 84 KiB |
|
@ -434,6 +434,10 @@ const tags = [
|
|||
slug: 'gay',
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
name: 'gloryhole',
|
||||
slug: 'gloryhole',
|
||||
},
|
||||
{
|
||||
name: 'gonzo',
|
||||
slug: 'gonzo',
|
||||
|
@ -973,6 +977,14 @@ const aliases = [
|
|||
name: 'big tits',
|
||||
for: 'big-boobs',
|
||||
},
|
||||
{
|
||||
name: 'busty - big boobs',
|
||||
for: 'big-boobs',
|
||||
},
|
||||
{
|
||||
name: 'busty: big beautiful breast',
|
||||
for: 'big-boobs',
|
||||
},
|
||||
{
|
||||
name: 'bi',
|
||||
for: 'bisexual',
|
||||
|
@ -1307,6 +1319,10 @@ const aliases = [
|
|||
name: 'gapes (gaping asshole)',
|
||||
for: 'gaping',
|
||||
},
|
||||
{
|
||||
name: 'glory hole',
|
||||
for: 'gloryhole',
|
||||
},
|
||||
{
|
||||
name: 'group sex',
|
||||
for: 'orgy',
|
||||
|
@ -1404,6 +1420,10 @@ const aliases = [
|
|||
name: 'red head',
|
||||
for: 'redhead',
|
||||
},
|
||||
{
|
||||
name: 'redhead babes',
|
||||
for: 'redhead',
|
||||
},
|
||||
{
|
||||
name: 'rimming',
|
||||
for: 'ass-eating',
|
||||
|
|
|
@ -72,6 +72,11 @@ const networks = [
|
|||
url: 'https://www.assylum.com',
|
||||
description: 'At Assylum, submissive girls get dominated with rough anal sex, ass to mouth, hard BDSM, and sexual humiliation and degradation.',
|
||||
},
|
||||
{
|
||||
slug: 'aziani',
|
||||
name: 'Aziani',
|
||||
url: 'https://www.aziani.com',
|
||||
},
|
||||
{
|
||||
slug: 'babes',
|
||||
name: 'Babes',
|
||||
|
|
|
@ -449,6 +449,66 @@ const sites = [
|
|||
a: 183,
|
||||
},
|
||||
},
|
||||
// AZIANI
|
||||
{
|
||||
slug: 'gangbangcreampie',
|
||||
name: 'Gangbang Creampie',
|
||||
url: 'https://www.gangbangcreampie.com',
|
||||
network: 'aziani',
|
||||
tags: ['gangbang', 'creampie'],
|
||||
},
|
||||
{
|
||||
slug: 'gloryholesecrets',
|
||||
name: 'Glory Hole Secrets',
|
||||
url: 'https://www.gloryholesecrets.com',
|
||||
network: 'aziani',
|
||||
tags: ['gloryhole'],
|
||||
},
|
||||
{
|
||||
slug: 'aziani',
|
||||
name: 'Aziani',
|
||||
url: 'https://www.aziani.com',
|
||||
network: 'aziani',
|
||||
},
|
||||
/* offline
|
||||
{
|
||||
slug: 'portagloryhole',
|
||||
name: 'Porta Gloryhole',
|
||||
url: 'https://www.portagloryhole.com',
|
||||
network: 'aziani',
|
||||
tags: ['gloryhole'],
|
||||
},
|
||||
{
|
||||
slug: 'azianiiron',
|
||||
name: 'Aziani Iron',
|
||||
url: 'https://www.azianiiron.com',
|
||||
network: 'aziani',
|
||||
},
|
||||
{
|
||||
slug: 'azianixposed',
|
||||
name: 'Aziani Xposed',
|
||||
url: 'https://www.azianixposed.com',
|
||||
network: 'aziani',
|
||||
},
|
||||
{
|
||||
slug: 'amberathome',
|
||||
name: 'Amber At Home',
|
||||
url: 'https://www.amberathome.com',
|
||||
network: 'aziani',
|
||||
},
|
||||
{
|
||||
slug: 'marycarey',
|
||||
name: 'Mary Carey',
|
||||
url: 'https://www.clubmarycarey.com',
|
||||
network: 'aziani',
|
||||
},
|
||||
{
|
||||
slug: 'racqueldevonshire',
|
||||
name: 'Racquel Devonshire',
|
||||
url: 'https://www.racqueldevonshire.com',
|
||||
network: 'aziani',
|
||||
},
|
||||
*/
|
||||
// BABES
|
||||
{
|
||||
name: 'Babes',
|
||||
|
|
|
@ -85,6 +85,7 @@ const tagPhotos = [
|
|||
['double-vaginal', 0, 'Aaliyah Hadid in "Squirting From Double Penetration With Anal" for Bang Bros'],
|
||||
['dv-tp', 1, 'Adriana Chechik in "Adriana\'s Triple Anal Penetration!"'],
|
||||
['dv-tp', 0, 'Luna Rival in LegalPorno SZ1490'],
|
||||
['facial', 1, 'Ella Knox in "Mr Saltys Adult Emporium Adventure 2" for Aziani'],
|
||||
['facial', 'poster', 'Jynx Maze'],
|
||||
['facefucking', 2, 'Jynx Maze for Throated'],
|
||||
['latina', 0, 'Abby Lee Brazil for Bang Bros'],
|
||||
|
|
|
@ -394,7 +394,7 @@ async function scrapeProfiles(sources, actorName, actorEntry, sitesBySlug) {
|
|||
const site = sitesBySlug[scraperSlug] || null;
|
||||
const profile = await scraper.fetchProfile(actorEntry ? actorEntry.name : actorName, scraperSlug, site, include);
|
||||
|
||||
if (profile) {
|
||||
if (profile && typeof profile !== 'number') {
|
||||
logger.verbose(`Found profile for '${actorName}' on ${scraperSlug}`);
|
||||
|
||||
return {
|
||||
|
@ -409,7 +409,7 @@ async function scrapeProfiles(sources, actorName, actorEntry, sitesBySlug) {
|
|||
};
|
||||
}
|
||||
|
||||
logger.verbose(`No profile for '${actorName}' available on ${scraperSlug}`);
|
||||
logger.verbose(`No profile for '${actorName}' available on ${scraperSlug}: ${profile}`);
|
||||
throw Object.assign(new Error(`Profile for ${actorName} not available on ${scraperSlug}`), { warn: false });
|
||||
}), Promise.reject(new Error()));
|
||||
} catch (error) {
|
||||
|
|
|
@ -81,6 +81,11 @@ async function associateActors(releases) {
|
|||
}, {});
|
||||
|
||||
const baseActors = Object.values(baseActorsByReleaseId).flat();
|
||||
|
||||
if (baseActors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseActorsBySlug = baseActors.reduce((acc, baseActor) => ({ ...acc, [baseActor.slug]: baseActor }), {});
|
||||
const uniqueBaseActors = Object.values(baseActorsBySlug);
|
||||
|
||||
|
|
12
src/app.js
|
@ -8,6 +8,7 @@ const fetchUpdates = require('./updates');
|
|||
const { fetchScenes, fetchMovies } = require('./deep');
|
||||
const { storeReleases } = require('./store-releases');
|
||||
const { updateReleasesSearch } = require('./releases');
|
||||
const { scrapeActors } = require('./actors-legacy');
|
||||
|
||||
async function init() {
|
||||
if (argv.server) {
|
||||
|
@ -17,12 +18,17 @@ async function init() {
|
|||
|
||||
if (argv.updateSearch) {
|
||||
await updateReleasesSearch();
|
||||
knex.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv.actors) {
|
||||
await scrapeActors(argv.actors);
|
||||
}
|
||||
|
||||
const updateBaseScenes = (argv.scrape || argv.sites || argv.networks) && await fetchUpdates();
|
||||
const deepScenes = argv.deep && await fetchScenes([...(argv.scenes || []), ...(updateBaseScenes || [])]);
|
||||
|
||||
const deepScenes = argv.deep
|
||||
? await fetchScenes([...(argv.scenes || []), ...(updateBaseScenes || [])])
|
||||
: updateBaseScenes;
|
||||
|
||||
const sceneMovies = deepScenes && argv.sceneMovies && deepScenes.map(scene => scene.movie).filter(Boolean);
|
||||
const deepMovies = await fetchMovies([...(argv.movies || []), ...(sceneMovies || [])]);
|
||||
|
|
12
src/knex.js
|
@ -3,17 +3,9 @@
|
|||
const config = require('config');
|
||||
const knex = require('knex');
|
||||
|
||||
/*
|
||||
module.exports = knex({
|
||||
client: 'sqlite3',
|
||||
connection: {
|
||||
filename: path.join(__dirname, '../db.sqlite'),
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
*/
|
||||
|
||||
module.exports = knex({
|
||||
client: 'pg',
|
||||
connection: config.database,
|
||||
// performance overhead, don't use asyncStackTraces in production
|
||||
asyncStackTraces: process.env.NODE_ENV === 'development',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
'use strict';
|
||||
|
||||
const slugify = require('../utils/slugify');
|
||||
const { get, getAll, initAll, extractDate } = require('../utils/qu');
|
||||
const { feetInchesToCm } = require('../utils/convert');
|
||||
|
||||
function getFallbacks(source) {
|
||||
return [
|
||||
source.replace('-1x.jpg', '-4x.jpg'),
|
||||
source.replace('-1x.jpg', '-3x.jpg'),
|
||||
source.replace('-1x.jpg', '-2x.jpg'),
|
||||
source,
|
||||
];
|
||||
}
|
||||
|
||||
function scrapeAll(scenes, site) {
|
||||
return scenes.map(({ qu }) => {
|
||||
const release = {};
|
||||
|
||||
release.entryId = qu.q('.stdimage', 'id', true).match(/set-target-(\d+)/)[1];
|
||||
release.url = qu.url('a');
|
||||
|
||||
release.title = qu.q('h5 a', true);
|
||||
release.date = qu.date('.icon-calendar + strong', 'MM/DD/YYYY');
|
||||
|
||||
release.actors = qu.q('h3', true).replace(/featuring:\s?/i, '').split(', ');
|
||||
|
||||
const photoCount = qu.q('.stdimage', 'cnt');
|
||||
[release.poster, ...release.photos] = Array.from({ length: Number(photoCount) }, (value, index) => {
|
||||
const source = qu.img('.stdimage', `src${index}_1x`, site.url);
|
||||
|
||||
return getFallbacks(source);
|
||||
});
|
||||
|
||||
return release;
|
||||
});
|
||||
}
|
||||
|
||||
function scrapeScene({ html, qu }, url) {
|
||||
const release = { url };
|
||||
|
||||
release.entryId = qu.q('.stdimage', 'id', true).match(/set-target-(\d+)/)[1];
|
||||
|
||||
release.title = qu.q('h2', true);
|
||||
release.description = qu.q('p', true);
|
||||
|
||||
release.date = extractDate(html, 'MM/DD/YYYY', /\b\d{2}\/\d{2}\/\d{4}\b/);
|
||||
|
||||
release.actors = qu.all('h5:not(.video_categories) a').map(actor => ({
|
||||
name: qu.q(actor, null, true),
|
||||
url: qu.url(actor, null),
|
||||
}));
|
||||
|
||||
release.tags = qu.all('.video_categories a', true);
|
||||
|
||||
release.duration = qu.dur('.video_categories + p');
|
||||
|
||||
const poster = qu.img('a img');
|
||||
|
||||
release.poster = getFallbacks(poster);
|
||||
release.photos = qu.imgs('.featured-video img', 'src0_1x').map(source => getFallbacks(source));
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
function scrapeProfile({ el, qu }) {
|
||||
const profile = {};
|
||||
|
||||
const bio = Array.from(qu.q('.widget-content').childNodes).reduce((acc, node, index, nodes) => {
|
||||
const nextNode = nodes[index + 1];
|
||||
|
||||
if (node.tagName === 'STRONG' && nextNode?.nodeType === 3) {
|
||||
acc[slugify(node.textContent, '_')] = nextNode.textContent.trim();
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (bio.ethnicity) profile.ethnicity = bio.ethnicity;
|
||||
if (bio.age) profile.age = Number(bio.age);
|
||||
|
||||
if (bio.height && /\d{3}/.test(bio.height)) profile.height = Number(bio.height.match(/\d+/)[0]);
|
||||
if (bio.height && /\d[;']\d/.test(bio.height)) profile.height = feetInchesToCm(bio.height);
|
||||
|
||||
if (bio.measurements) {
|
||||
const [bust, waist, hip] = bio.measurements.split('-');
|
||||
|
||||
if (bust && /\d+[a-zA-Z]+/.test(bust)) profile.bust = bust;
|
||||
if (waist) profile.waist = Number(waist);
|
||||
if (hip) profile.hip = Number(hip);
|
||||
}
|
||||
|
||||
if (bio.bust_size && !profile.bust) profile.bust = bio.bust_size.toUpperCase();
|
||||
|
||||
if (bio.birth_location) profile.birthPlace = bio.birth_location;
|
||||
if (bio.status_married_or_single) profile.relationship = bio.status_married_or_single;
|
||||
|
||||
if (bio.eye_color) profile.eyes = bio.eye_color;
|
||||
|
||||
const avatar = qu.img('.tac img');
|
||||
profile.avatar = getFallbacks(avatar);
|
||||
|
||||
profile.releases = scrapeAll(initAll(el, '.featured-video'));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
async function fetchLatest(site, page) {
|
||||
const url = `${site.url}/tour/categories/movies_${page}_d.html`;
|
||||
const res = await getAll(url, '.featured-video');
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeAll(res.items, site);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchScene(url, site) {
|
||||
const res = await get(url, '.page-content .row');
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeScene(res.item, url, site);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchProfile(actorName, scraperSlug, site) {
|
||||
const actorSlug = slugify(actorName, '');
|
||||
const url = `${site.url}/tour/models/${actorSlug}.html`;
|
||||
const res = await get(url, '.page-content .row');
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeProfile(res.item);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchLatest,
|
||||
fetchProfile,
|
||||
fetchScene,
|
||||
};
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const adulttime = require('./adulttime');
|
||||
const assylum = require('./assylum');
|
||||
const aziani = require('./aziani');
|
||||
const amateurallure = require('./amateurallure');
|
||||
const babes = require('./babes');
|
||||
const bamvisions = require('./bamvisions');
|
||||
|
@ -70,6 +71,7 @@ module.exports = {
|
|||
adulttime,
|
||||
amateurallure,
|
||||
assylum,
|
||||
aziani,
|
||||
babes,
|
||||
bamvisions,
|
||||
bang,
|
||||
|
@ -132,6 +134,7 @@ module.exports = {
|
|||
analized: fullpornnetwork,
|
||||
analviolation: fullpornnetwork,
|
||||
anilos: nubiles,
|
||||
aziani,
|
||||
babes,
|
||||
baddaddypov: fullpornnetwork,
|
||||
bamvisions,
|
||||
|
@ -155,7 +158,9 @@ module.exports = {
|
|||
famedigital,
|
||||
freeones,
|
||||
freeonesLegacy,
|
||||
gangbangcreampie: aziani,
|
||||
girlfaction: fullpornnetwork,
|
||||
gloryholesecrets: aziani,
|
||||
hergape: fullpornnetwork,
|
||||
homemadeanalwhores: fullpornnetwork,
|
||||
hotcrazymess: nubiles,
|
||||
|
@ -187,8 +192,8 @@ module.exports = {
|
|||
private: privateNetwork,
|
||||
realitykings,
|
||||
score,
|
||||
sexyhub: mindgeek,
|
||||
seehimfuck: hush,
|
||||
sexyhub: mindgeek,
|
||||
thatsitcomshow: nubiles,
|
||||
transangels,
|
||||
tushy: vixen,
|
||||
|
|
|
@ -34,11 +34,15 @@ function formatDate(dateValue, format, inputFormat) {
|
|||
return moment(dateValue).format(format);
|
||||
}
|
||||
|
||||
function prefixProtocol(urlValue, protocol = 'https') {
|
||||
function prefixUrl(urlValue, origin, protocol = 'https') {
|
||||
if (protocol && /^\/\//.test(urlValue)) {
|
||||
return `${protocol}:${urlValue}`;
|
||||
}
|
||||
|
||||
if (origin && /^\//.test(urlValue)) {
|
||||
return `${origin}${urlValue}`;
|
||||
}
|
||||
|
||||
return urlValue;
|
||||
}
|
||||
|
||||
|
@ -48,7 +52,7 @@ function q(context, selector, attrArg, applyTrim = true) {
|
|||
if (attr) {
|
||||
const value = selector
|
||||
? context.querySelector(selector)?.[attr] || context.querySelector(selector)?.attributes[attr]?.value
|
||||
: context[attr] || context[attr]?.attributes[attr]?.value;
|
||||
: context[attr] || context.attributes[attr]?.value;
|
||||
|
||||
return applyTrim && value ? trim(value) : value;
|
||||
}
|
||||
|
@ -60,7 +64,7 @@ function all(context, selector, attrArg, applyTrim = true) {
|
|||
const attr = attrArg === true ? 'textContent' : attrArg;
|
||||
|
||||
if (attr) {
|
||||
return Array.from(context.querySelectorAll(selector), el => (applyTrim && el[attr] ? trim(el[attr]) : el[attr]));
|
||||
return Array.from(context.querySelectorAll(selector), el => q(el, null, attr, applyTrim));
|
||||
}
|
||||
|
||||
return Array.from(context.querySelectorAll(selector));
|
||||
|
@ -112,47 +116,47 @@ function date(context, selector, format, match, attr = 'textContent') {
|
|||
return extractDate(dateString, format, match);
|
||||
}
|
||||
|
||||
function image(context, selector = 'img', attr = 'src', protocol = 'https') {
|
||||
function image(context, selector = 'img', attr = 'src', origin, protocol = 'https') {
|
||||
const imageEl = q(context, selector, attr);
|
||||
|
||||
// no attribute means q output will be HTML element
|
||||
return attr ? prefixProtocol(imageEl, protocol) : imageEl;
|
||||
return attr ? prefixUrl(imageEl, origin, protocol) : imageEl;
|
||||
}
|
||||
|
||||
function images(context, selector = 'img', attr = 'src', protocol = 'https') {
|
||||
function images(context, selector = 'img', attr = 'src', origin, protocol = 'https') {
|
||||
const imageEls = all(context, selector, attr);
|
||||
|
||||
return attr ? imageEls.map(imageEl => prefixProtocol(imageEl, protocol)) : imageEls;
|
||||
return attr ? imageEls.map(imageEl => prefixUrl(imageEl, origin, protocol)) : imageEls;
|
||||
}
|
||||
|
||||
function url(context, selector = 'a', attr = 'href', protocol = 'https') {
|
||||
function url(context, selector = 'a', attr = 'href', origin, protocol = 'https') {
|
||||
const urlEl = q(context, selector, attr);
|
||||
|
||||
return attr ? prefixProtocol(urlEl, protocol) : urlEl;
|
||||
return attr ? prefixUrl(urlEl, origin, protocol) : urlEl;
|
||||
}
|
||||
|
||||
function urls(context, selector = 'a', attr = 'href', protocol = 'https') {
|
||||
function urls(context, selector = 'a', attr = 'href', origin, protocol = 'https') {
|
||||
const urlEls = all(context, selector, attr);
|
||||
|
||||
return attr ? urlEls.map(urlEl => prefixProtocol(urlEl, protocol)) : urlEls;
|
||||
return attr ? urlEls.map(urlEl => prefixUrl(urlEl, origin, protocol)) : urlEls;
|
||||
}
|
||||
|
||||
function poster(context, selector = 'video', attr = 'poster', protocol = 'https') {
|
||||
function poster(context, selector = 'video', attr = 'poster', origin, protocol = 'https') {
|
||||
const posterEl = q(context, selector, attr);
|
||||
|
||||
return attr ? prefixProtocol(posterEl, protocol) : posterEl;
|
||||
return attr ? prefixUrl(posterEl, origin, protocol) : posterEl;
|
||||
}
|
||||
|
||||
function video(context, selector = 'source', attr = 'src', protocol = 'https') {
|
||||
function video(context, selector = 'source', attr = 'src', origin, protocol = 'https') {
|
||||
const trailerEl = q(context, selector, attr);
|
||||
|
||||
return attr ? prefixProtocol(trailerEl, protocol) : trailerEl;
|
||||
return attr ? prefixUrl(trailerEl, origin, protocol) : trailerEl;
|
||||
}
|
||||
|
||||
function videos(context, selector = 'source', attr = 'src', protocol = 'https') {
|
||||
function videos(context, selector = 'source', attr = 'src', origin, protocol = 'https') {
|
||||
const trailerEls = all(context, selector, attr);
|
||||
|
||||
return attr ? trailerEls.map(trailerEl => prefixProtocol(trailerEl, protocol)) : trailerEls;
|
||||
return attr ? trailerEls.map(trailerEl => prefixUrl(trailerEl, origin, protocol)) : trailerEls;
|
||||
}
|
||||
|
||||
function duration(context, selector, match, attr = 'textContent') {
|
||||
|
|