Compare commits
No commits in common. "46c0b269c339fff3508f00cfbce283c141e281a2" and "d3d08b9c218e1e9ef0cbcb4b99f4842f03a4336f" have entirely different histories.
46c0b269c3
...
d3d08b9c21
|
@ -51,7 +51,7 @@
|
|||
<Expand
|
||||
v-if="bioExpanded"
|
||||
:expanded="bioExpanded"
|
||||
class="expand expand-light"
|
||||
class="expand expand-dark"
|
||||
@expand="(state) => bioExpanded = state"
|
||||
/>
|
||||
|
||||
|
@ -310,7 +310,7 @@
|
|||
|
||||
<Expand
|
||||
:expanded="bioExpanded"
|
||||
class="expand expand-light"
|
||||
class="expand expand-dark"
|
||||
@expand="(state) => bioExpanded = state"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.160.4",
|
||||
"version": "1.160.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.160.4",
|
||||
"version": "1.160.3",
|
||||
"description": "All the latest porn releases in one place",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 410 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 261 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 541 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 353 KiB |
Before Width: | Height: | Size: 24 KiB |
|
@ -43,7 +43,6 @@ const parentNetworks = [
|
|||
url: 'https://www.21sextury.com',
|
||||
description: 'Watch all the latest scenes and porn video updates on 21Sextury.com, the best European porn site with the hottest pornstars from all over the world! Watch porn videos from the large network here.',
|
||||
parameters: {
|
||||
layout: 'api',
|
||||
mobile: 'https://m.dpfanatics.com/en/video',
|
||||
},
|
||||
parent: 'gamma',
|
||||
|
@ -57,7 +56,6 @@ const networks = [
|
|||
url: 'https://www.21sextreme.com',
|
||||
description: 'Welcome to 21Sextreme.com, your portal to fisting porn, old and young lesbians, horny grannies & extreme BDSM featuring the best Euro & American Pornstars',
|
||||
parameters: {
|
||||
layout: 'api',
|
||||
mobile: 'https://m.dpfanatics.com/en/video',
|
||||
},
|
||||
parent: '21sextury',
|
||||
|
@ -68,7 +66,6 @@ const networks = [
|
|||
url: 'https://www.21naturals.com',
|
||||
description: 'Welcome to 21Naturals.com, the porn network featuring the hottest pornstars from all over the world in all natural porn and erotic sex videos. Watch thousands of girls with natural tits',
|
||||
parameters: {
|
||||
layout: 'api',
|
||||
mobile: 'https://m.dpfanatics.com/en/video',
|
||||
},
|
||||
parent: '21sextury',
|
||||
|
@ -128,7 +125,6 @@ const networks = [
|
|||
description: 'Welcome to Blowpass.com, your ultimate source for deepthroat porn, MILF and teen blowjob videos, big cumshots and any and everything oral!',
|
||||
parameters: {
|
||||
mobile: 'https://m.blowpass.com/en/video/v/%d', // v can be any string, %d will be scene ID
|
||||
actorScenes: 'https://www.blowpass.com/en/videos/blowpass/latest/All-Categories/0{path}/{page}',
|
||||
},
|
||||
parent: 'gamma',
|
||||
},
|
||||
|
|
|
@ -4710,13 +4710,6 @@ const sites = [
|
|||
tags: ['anal'],
|
||||
parent: 'mikeadriano',
|
||||
},
|
||||
{
|
||||
slug: 'analonly',
|
||||
name: 'Anal Only',
|
||||
url: 'https://analonly.com',
|
||||
tags: ['anal'],
|
||||
parent: 'mikeadriano',
|
||||
},
|
||||
{
|
||||
slug: 'allanal',
|
||||
name: 'All Anal',
|
||||
|
|
|
@ -685,13 +685,12 @@ const tagPhotos = [
|
|||
['69', 2, 'Abigail Mac and Kissa Sins in "Lesbian Anal Workout" for HardX'],
|
||||
['airtight', 7, 'Lana Rhoades in "Gangbang Me 3" for HardX'],
|
||||
['airtight', 6, 'Remy Lacroix in "Ass Worship 14" for Jules Jordan'],
|
||||
['airtight', 11, 'Malena Nazionale in "Rocco\'s Perverted Secretaries 2: Italian Edition" for Rocco Siffredi'],
|
||||
['airtight', 1, 'Jynx Maze in "Pump My Ass Full of Cum 3" for Jules Jordan'],
|
||||
['airtight', 10, 'Asa Akira in "Asa Akira To The Limit" for Jules Jordan'],
|
||||
['airtight', 8, 'Veronica Leal in LegalPorno SZ2520'],
|
||||
['airtight', 3, 'Anita Bellini in "Triple Dick Gangbang" for Hands On Hardcore (DDF Network)'],
|
||||
['airtight', 5, 'Chloe Amour in "DP Masters 4" for Jules Jordan'],
|
||||
['airtight', 3, 'Anita Bellini in "Triple Dick Gangbang" for Hands On Hardcore (DDF Network)'],
|
||||
['airtight', 9, 'Cindy Shine in LegalPorno GP1658'],
|
||||
['airtight', 1, 'Jynx Maze in "Pump My Ass Full of Cum 3" for Jules Jordan'],
|
||||
['atm', 3, 'Natasha Teen in "Work That Ass!" for Her Limit'],
|
||||
['atm', 0, 'Roxy Lips in "Under Her Coat" for 21 Naturals'],
|
||||
['atm', 6, 'Jane Wilde in "Teen Anal" for Evil Angel'],
|
||||
|
@ -874,11 +873,10 @@ const tagPhotos = [
|
|||
['orgy', 'poster', 'Zoey Mornoe (DP), Jillian Janson (sex), Frida Sante, Katerina Kay and Natasha Starr in "Orgy Masters 6" for Jules Jordan'],
|
||||
['pussy-eating', 4, 'Anastasia Knight and Jillian Janson in "Teach Me" for Screwbox'],
|
||||
['pussy-eating', 7, 'Jewelz Blu and Katie Kush in "Pick Your Pleasure" for Reality Kings'],
|
||||
['pussy-eating', 8, 'Sia Lust and Lacey London in "Naughty Gamer Girls" for Girls Gone Pink'],
|
||||
['pussy-eating', 6, 'Abella Danger and Karma Rx in "Neon Dreaming" for Brazzers'],
|
||||
['pussy-eating', 0, 'Kali Roses and Emily Willis\' pussy in "Peeping On My Neighbor" for Girl Girl'],
|
||||
['pussy-eating', 2, 'Anikka Albrite and Mia Malkova in "Big Anal Bombshells" for LesbianX'],
|
||||
['pussy-eating', 3, 'Kylie Page and Kalina Ryu in "Training My Masseuse" for All Girl Massage'],
|
||||
['pussy-eating', 6, 'Abella Danger and Karma Rx in "Neon Dreaming" for Brazzers'],
|
||||
['pussy-eating', 1, 'Anikka Albrite and Riley Reid for In The Crack'],
|
||||
['redhead', 0, 'Penny Pax in "The Submission of Emma Marx: Boundaries" for New Sensations'],
|
||||
['schoolgirl', 1, 'Eliza Ibarra for Brazzers'],
|
||||
|
|
|
@ -622,10 +622,7 @@ async function scrapeProfiles(actor, sources, entitiesBySlug, existingProfilesBy
|
|||
const entity = entitiesBySlug[scraperSlug] || null;
|
||||
|
||||
const scraper = scrapers[scraperSlug];
|
||||
const layoutScraper = scraper?.[entity.parameters?.layout]
|
||||
|| scraper?.[entity.parent?.parameters?.layout]
|
||||
|| scraper?.[entity.parent?.parent?.parameters?.layout]
|
||||
|| scraper;
|
||||
const layoutScraper = scraper?.[entity.parameters?.layout] || scraper?.[entity.parent?.parameters?.layout] || scraper?.[entity.parent?.parent?.parameters?.layout] || scraper;
|
||||
|
||||
const context = {
|
||||
...entity,
|
||||
|
@ -634,11 +631,6 @@ async function scrapeProfiles(actor, sources, entitiesBySlug, existingProfilesBy
|
|||
network: entity?.parent,
|
||||
entity,
|
||||
scraper: scraperSlug,
|
||||
parameters: {
|
||||
...entity?.parent?.parent?.parameters,
|
||||
...entity?.parent?.parameters,
|
||||
...entity?.parameters,
|
||||
},
|
||||
};
|
||||
|
||||
const label = context.entity?.name;
|
||||
|
|
80
src/deep.js
|
@ -5,11 +5,49 @@ const merge = require('object-merge-advanced');
|
|||
|
||||
const argv = require('./argv');
|
||||
const include = require('./utils/argv-include')(argv);
|
||||
const { fetchReleaseEntities, urlToSiteSlug } = require('./entities');
|
||||
const logger = require('./logger')(__filename);
|
||||
const knex = require('./knex');
|
||||
const qu = require('./utils/qu');
|
||||
const scrapers = require('./scrapers/scrapers');
|
||||
|
||||
function urlToSiteSlug(url) {
|
||||
try {
|
||||
const slug = new URL(url)
|
||||
.hostname
|
||||
.match(/([\w-]+)\.\w+$/)?.[1]
|
||||
.replace(/[-_]+/g, '');
|
||||
|
||||
return slug;
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to derive entity slug from '${url}': ${error.message}`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function findEntities(baseReleases) {
|
||||
const baseReleasesWithoutEntity = baseReleases.filter(release => release.url && !release.site && !release.entity);
|
||||
|
||||
const entitySlugs = Array.from(new Set(
|
||||
baseReleasesWithoutEntity
|
||||
.map(baseRelease => urlToSiteSlug(baseRelease.url))
|
||||
.filter(Boolean),
|
||||
));
|
||||
|
||||
const entities = await knex('entities')
|
||||
.select(knex.raw('entities.*, row_to_json(parents) as parent, json_agg(children) as children'))
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.leftJoin('entities as children', 'children.parent_id', 'entities.id')
|
||||
.whereIn('entities.slug', entitySlugs)
|
||||
.groupBy('entities.id', 'parents.id')
|
||||
.orderBy('entities.type', 'asc');
|
||||
|
||||
// channel entity will overwrite network entity
|
||||
const entitiesBySlug = entities.reduce((accEntities, entity) => ({ ...accEntities, [entity.slug]: accEntities[entity.slug] || entity }), {});
|
||||
|
||||
return entitiesBySlug;
|
||||
}
|
||||
|
||||
function toBaseReleases(baseReleasesOrUrls, entity = null) {
|
||||
if (!baseReleasesOrUrls) {
|
||||
return [];
|
||||
|
@ -68,32 +106,8 @@ async function fetchScene(scraper, url, entity, baseRelease, options) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function findScraper(entity) {
|
||||
if (scrapers.releases[entity.slug]) {
|
||||
return scrapers.releases[entity.slug];
|
||||
}
|
||||
|
||||
if (entity.parent) {
|
||||
return findScraper(entity.parent);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findLayoutScraper(entity, scraper) {
|
||||
if (scraper?.[entity.parameters?.layout]) {
|
||||
return scraper[entity.parameters.layout];
|
||||
}
|
||||
|
||||
if (entity.parent) {
|
||||
return findLayoutScraper(entity.parent, scraper);
|
||||
}
|
||||
|
||||
return scraper;
|
||||
}
|
||||
|
||||
async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
|
||||
const entity = baseRelease.entity || entitiesBySlug[urlToSiteSlug(baseRelease.url)];
|
||||
async function scrapeRelease(baseRelease, entities, type = 'scene') {
|
||||
const entity = baseRelease.entity || entities[urlToSiteSlug(baseRelease.url)];
|
||||
|
||||
if (!entity) {
|
||||
logger.warn(`No entity available for ${baseRelease.url}`);
|
||||
|
@ -107,8 +121,8 @@ async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
|
|||
};
|
||||
}
|
||||
|
||||
const scraper = findScraper(entity);
|
||||
const layoutScraper = findLayoutScraper(entity, scraper);
|
||||
const scraper = scrapers.releases[entity.slug] || scrapers.releases[entity.parent?.slug] || scrapers.releases[entity.parent?.parent?.slug];
|
||||
const layoutScraper = scraper?.[entity.parameters?.layout] || scraper?.[entity.parent?.parameters?.layout] || scraper?.[entity.parent?.parent?.parameters?.layout] || scraper;
|
||||
|
||||
if (!layoutScraper) {
|
||||
logger.warn(`Could not find scraper for ${baseRelease.url}`);
|
||||
|
@ -170,19 +184,19 @@ async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
|
|||
}
|
||||
}
|
||||
|
||||
async function scrapeReleases(baseReleases, entitiesBySlug, type) {
|
||||
async function scrapeReleases(baseReleases, entities, type) {
|
||||
return Promise.map(
|
||||
baseReleases,
|
||||
async baseRelease => scrapeRelease(baseRelease, entitiesBySlug, type),
|
||||
async baseRelease => scrapeRelease(baseRelease, entities, type),
|
||||
{ concurrency: 10 },
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchReleases(baseReleasesOrUrls, type = 'scene') {
|
||||
const baseReleases = toBaseReleases(baseReleasesOrUrls);
|
||||
const entitiesBySlug = await fetchReleaseEntities(baseReleases);
|
||||
const entities = await findEntities(baseReleases);
|
||||
|
||||
const deepReleases = await scrapeReleases(baseReleases, entitiesBySlug, type);
|
||||
const deepReleases = await scrapeReleases(baseReleases, entities, type);
|
||||
|
||||
return deepReleases.filter(Boolean);
|
||||
}
|
||||
|
|
|
@ -66,21 +66,6 @@ async function curateEntities(entities, includeParameters) {
|
|||
return Promise.all(entities.map(async entity => curateEntity(entity, includeParameters)));
|
||||
}
|
||||
|
||||
function urlToSiteSlug(url) {
|
||||
try {
|
||||
const slug = new URL(url)
|
||||
.hostname
|
||||
.match(/([\w-]+)\.\w+$/)?.[1]
|
||||
.replace(/[-_]+/g, '');
|
||||
|
||||
return slug;
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to derive entity slug from '${url}': ${error.message}`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIncludedEntities() {
|
||||
const include = {
|
||||
includeAll: !argv.networks && !argv.channels && !config.include?.networks && !config.include?.channels,
|
||||
|
@ -154,46 +139,6 @@ async function fetchIncludedEntities() {
|
|||
return curatedNetworks;
|
||||
}
|
||||
|
||||
async function fetchReleaseEntities(baseReleases) {
|
||||
const baseReleasesWithoutEntity = baseReleases.filter(release => release.url && !release.site && !release.entity);
|
||||
|
||||
const entitySlugs = Array.from(new Set(
|
||||
baseReleasesWithoutEntity
|
||||
.map(baseRelease => urlToSiteSlug(baseRelease.url))
|
||||
.filter(Boolean),
|
||||
));
|
||||
|
||||
const entities = await knex.raw(`
|
||||
WITH RECURSIVE tree as (
|
||||
SELECT to_jsonb(entities) as entity,
|
||||
parent_id,
|
||||
array['parent'] as parent_path,
|
||||
0 as depth
|
||||
FROM entities
|
||||
WHERE slug = ANY(:entitySlugs)
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT jsonb_set(tree.entity, tree.parent_path, to_jsonb(entities)),
|
||||
entities.parent_id,
|
||||
tree.parent_path || array['parent'],
|
||||
depth + 1
|
||||
FROM tree
|
||||
JOIN entities ON tree.parent_id = entities.id
|
||||
)
|
||||
SELECT entity FROM tree WHERE parent_id is null
|
||||
ORDER BY entity->'type' ASC;
|
||||
`, { entitySlugs });
|
||||
|
||||
// channel entity will overwrite network entity
|
||||
const entitiesBySlug = entities.rows.reduce((accEntities, { entity }) => ({
|
||||
...accEntities,
|
||||
[entity.slug]: accEntities[entity.slug] || curateEntity(entity, true),
|
||||
}), {});
|
||||
|
||||
return entitiesBySlug;
|
||||
}
|
||||
|
||||
async function fetchEntity(entityId, type) {
|
||||
const entity = await knex('entities')
|
||||
.select(knex.raw(`
|
||||
|
@ -345,10 +290,8 @@ module.exports = {
|
|||
curateEntity,
|
||||
curateEntities,
|
||||
fetchIncludedEntities,
|
||||
fetchReleaseEntities,
|
||||
fetchEntity,
|
||||
fetchEntities,
|
||||
searchEntities,
|
||||
flushEntities,
|
||||
urlToSiteSlug,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma');
|
||||
|
||||
module.exports = {
|
||||
fetchLatest: fetchApiLatest,
|
||||
fetchProfile: fetchApiProfile,
|
||||
fetchUpcoming: fetchApiUpcoming,
|
||||
fetchScene,
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma');
|
||||
|
||||
module.exports = {
|
||||
fetchLatest: fetchApiLatest,
|
||||
fetchProfile: fetchApiProfile,
|
||||
fetchUpcoming: fetchApiUpcoming,
|
||||
fetchScene,
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma');
|
||||
|
||||
module.exports = {
|
||||
fetchLatest: fetchApiLatest,
|
||||
fetchProfile: fetchApiProfile,
|
||||
fetchUpcoming: fetchApiUpcoming,
|
||||
fetchScene,
|
||||
};
|
|
@ -19,9 +19,17 @@ async function fetchSceneWrapper(url, site, baseRelease, options) {
|
|||
return release;
|
||||
}
|
||||
|
||||
function getActorReleasesUrl(actorPath, page = 1) {
|
||||
return `https://www.blowpass.com/en/videos/blowpass/latest/All-Categories/0${actorPath}/${page}`;
|
||||
}
|
||||
|
||||
async function networkFetchProfile({ name: actorName }, context, include) {
|
||||
return fetchProfile({ name: actorName }, context, null, getActorReleasesUrl, include);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchLatest,
|
||||
fetchProfile,
|
||||
fetchProfile: networkFetchProfile,
|
||||
fetchUpcoming,
|
||||
fetchScene: fetchSceneWrapper,
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@ const util = require('util');
|
|||
const { JSDOM } = require('jsdom');
|
||||
const cheerio = require('cheerio');
|
||||
const moment = require('moment');
|
||||
const format = require('template-format');
|
||||
|
||||
const logger = require('../logger')(__filename);
|
||||
const qu = require('../utils/qu');
|
||||
|
@ -377,34 +376,26 @@ function scrapeActorSearch(html, url, actorName) {
|
|||
return actorLink ? actorLink.href : null;
|
||||
}
|
||||
|
||||
async function fetchActorReleases(profileUrl, getActorReleasesUrl, page = 1, accReleases = [], context) {
|
||||
async function fetchActorReleases(profileUrl, getActorReleasesUrl, page = 1, accReleases = []) {
|
||||
const { origin, pathname } = new URL(profileUrl);
|
||||
const profilePath = `/${pathname.split('/').slice(-2).join('/')}`;
|
||||
|
||||
const url = (context.parameters.actorScenes && format(context.parameters.actorScenes, { path: profilePath, page }))
|
||||
|| getActorReleasesUrl?.(profilePath, page);
|
||||
|
||||
if (!url) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const url = getActorReleasesUrl(profilePath, page);
|
||||
const res = await qu.get(url);
|
||||
|
||||
if (!res.ok) {
|
||||
return [];
|
||||
}
|
||||
if (!res.ok) return [];
|
||||
|
||||
const releases = scrapeAll(res.item.html, null, origin);
|
||||
const nextPage = res.item.query.url('.Gamma_Paginator a.next');
|
||||
|
||||
if (nextPage) {
|
||||
return fetchActorReleases(profileUrl, getActorReleasesUrl, page + 1, accReleases.concat(releases), context);
|
||||
return fetchActorReleases(profileUrl, getActorReleasesUrl, page + 1, accReleases.concat(releases));
|
||||
}
|
||||
|
||||
return accReleases.concat(releases);
|
||||
}
|
||||
|
||||
async function scrapeProfile(html, url, actorName, _siteSlug, getActorReleasesUrl, withReleases, context) {
|
||||
async function scrapeProfile(html, url, actorName, _siteSlug, getActorReleasesUrl, withReleases) {
|
||||
const { query } = qu.extract(html);
|
||||
|
||||
const avatar = query.el('img.actorPicture');
|
||||
|
@ -438,8 +429,8 @@ async function scrapeProfile(html, url, actorName, _siteSlug, getActorReleasesUr
|
|||
if (alias) profile.aliases = alias.split(':')[1].trim().split(', ');
|
||||
if (nationality) profile.nationality = nationality.split(':')[1].trim();
|
||||
|
||||
if ((getActorReleasesUrl || context.parameters.actorScenes) && withReleases) {
|
||||
profile.releases = await fetchActorReleases(url, getActorReleasesUrl, 1, [], context);
|
||||
if (getActorReleasesUrl && withReleases) {
|
||||
profile.releases = await fetchActorReleases(url, getActorReleasesUrl);
|
||||
}
|
||||
|
||||
return profile;
|
||||
|
@ -670,7 +661,7 @@ async function fetchActorScenes(actorName, apiUrl, siteSlug) {
|
|||
return [];
|
||||
}
|
||||
|
||||
async function fetchProfile({ name: actorName }, context, include, altSearchUrl, getActorReleasesUrl) {
|
||||
async function fetchProfile({ name: actorName }, context, altSearchUrl, getActorReleasesUrl, include) {
|
||||
const siteSlug = context.entity.slug || context.site?.slug || context.network?.slug;
|
||||
|
||||
const actorSlug = actorName.toLowerCase().replace(/\s+/, '+');
|
||||
|
@ -693,7 +684,7 @@ async function fetchProfile({ name: actorName }, context, include, altSearchUrl,
|
|||
return null;
|
||||
}
|
||||
|
||||
return scrapeProfile(actorRes.body.toString(), url, actorName, siteSlug, getActorReleasesUrl, include.scenes, context);
|
||||
return scrapeProfile(actorRes.body.toString(), url, actorName, siteSlug, getActorReleasesUrl, include.scenes);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -67,29 +67,6 @@ async function fetchLatest(channel, page = 1) {
|
|||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchUpcoming(channel) {
|
||||
const { host } = new URL(channel.url);
|
||||
const url = `https://tour.${host}`;
|
||||
|
||||
const res = await qu.get(url);
|
||||
|
||||
if (res.ok) {
|
||||
if (res.item.query.exists('a[href*="stackpath.com"]')) {
|
||||
throw new Error('URL blocked by StackPath');
|
||||
}
|
||||
|
||||
const sceneItem = qu.init(res.item.el, '#upcoming-content');
|
||||
|
||||
if (sceneItem) {
|
||||
return scrapeAll([sceneItem], channel);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchScene(url, channel) {
|
||||
const cookieJar = http.cookieJar();
|
||||
const session = http.session({ cookieJar });
|
||||
|
@ -145,7 +122,6 @@ async function fetchProfile({ name: actorName }, context , site) {
|
|||
|
||||
module.exports = {
|
||||
fetchLatest,
|
||||
fetchUpcoming,
|
||||
// fetchProfile,
|
||||
fetchScene,
|
||||
};
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
const blake2 = require('blake2');
|
||||
const knex = require('../knex');
|
||||
|
||||
const qu = require('../utils/qu');
|
||||
const { ex, ctxa } = require('../utils/q');
|
||||
const http = require('../utils/http');
|
||||
|
||||
async function getSiteSlugs() {
|
||||
return knex('entities')
|
||||
.pluck('entities.slug')
|
||||
.join('entities AS parents', 'parents.id', 'entities.parent_id')
|
||||
.where('parents.slug', 'perfectgonzo');
|
||||
return knex('sites')
|
||||
.pluck('sites.slug')
|
||||
.join('networks', 'networks.id', 'sites.network_id')
|
||||
.where('networks.slug', 'perfectgonzo');
|
||||
}
|
||||
|
||||
function getHash(identifier) {
|
||||
|
@ -38,10 +39,8 @@ function extractMaleModelsFromTags(tagContainer) {
|
|||
return [];
|
||||
}
|
||||
|
||||
async function extractChannelFromPhoto(photo, channel) {
|
||||
const siteSlugs = (channel.type === 'network' ? channel.children : channel.parent?.children)?.map(child => child.slug)
|
||||
|| await getSiteSlugs();
|
||||
|
||||
async function extractChannelFromPhoto(photo, metaSiteSlugs) {
|
||||
const siteSlugs = metaSiteSlugs || await getSiteSlugs();
|
||||
const channelMatch = photo.match(new RegExp(siteSlugs.join('|')));
|
||||
|
||||
if (channelMatch) {
|
||||
|
@ -51,50 +50,66 @@ async function extractChannelFromPhoto(photo, channel) {
|
|||
return null;
|
||||
}
|
||||
|
||||
async function scrapeLatest(scenes, site) {
|
||||
return scenes.map(({ query }) => {
|
||||
const release = {};
|
||||
async function scrapeLatest(html, site) {
|
||||
const siteSlugs = await getSiteSlugs();
|
||||
const { element } = ex(html);
|
||||
|
||||
release.title = query.q('a', 'title');
|
||||
release.url = query.url('a', 'href', { origin: site.url });
|
||||
release.date = query.date('.nm-date', 'MM/DD/YYYY');
|
||||
return ctxa(element, '#content-main .itemm').map(({
|
||||
q, qa, qlength, qdate, qimages,
|
||||
}) => {
|
||||
const release = {
|
||||
site,
|
||||
meta: {
|
||||
siteSlugs,
|
||||
},
|
||||
};
|
||||
|
||||
const sceneLink = q('a');
|
||||
|
||||
release.title = sceneLink.title;
|
||||
release.url = `${site.url}${sceneLink.href}`;
|
||||
release.date = qdate('.nm-date', 'MM/DD/YYYY');
|
||||
|
||||
const slug = new URL(release.url).pathname.split('/')[2];
|
||||
release.entryId = getHash(`${site.slug}${slug}${release.date.toISOString()}`);
|
||||
|
||||
release.actors = release.title.split('&').map(actor => actor.trim());
|
||||
|
||||
[release.poster, ...release.photos] = query.imgs('.bloc-link img');
|
||||
[release.poster, ...release.photos] = qimages('.bloc-link img');
|
||||
|
||||
release.tags = query.cnts('.dropdown ul a').slice(1);
|
||||
release.duration = query.duration('.dropdown p:first-child');
|
||||
release.tags = qa('.dropdown ul a', true).slice(1);
|
||||
release.duration = qlength('.dropdown p:first-child');
|
||||
|
||||
return release;
|
||||
});
|
||||
}
|
||||
|
||||
async function scrapeScene({ query }, site, url) {
|
||||
async function scrapeScene(html, site, url, metaSiteSlugs) {
|
||||
const {
|
||||
q, qa, qlength, qdate, qposter, qtrailer,
|
||||
} = ex(html);
|
||||
|
||||
const release = { url, site };
|
||||
|
||||
release.title = query.cnt('#movie-header h2');
|
||||
release.date = query.date('#movie-header div span', 'MMMM DD, YYYY', /\w+ \d{1,2}, \d{4}/);
|
||||
release.title = q('#movie-header h2', true);
|
||||
release.date = qdate('#movie-header div span', 'MMMM DD, YYYY', /\w+ \d{1,2}, \d{4}/);
|
||||
|
||||
release.description = query.cnt('.container .mg-md');
|
||||
release.duration = query.duration('#video-ribbon .container > div > span:nth-child(3)');
|
||||
release.description = q('.container .mg-md', true);
|
||||
release.duration = qlength('#video-ribbon .container > div > span:nth-child(3)');
|
||||
|
||||
release.actors = query.cnts('#video-info a').concat(extractMaleModelsFromTags(query.q('.tag-container')));
|
||||
release.tags = query.cnts('.tag-container a');
|
||||
release.actors = qa('#video-info a', true).concat(extractMaleModelsFromTags(q('.tag-container')));
|
||||
release.tags = qa('.tag-container a', true);
|
||||
|
||||
const uhd = query.cnt('#video-ribbon .container > div > span:nth-child(2)');
|
||||
const uhd = q('#video-ribbon .container > div > span:nth-child(2)', true);
|
||||
if (/4K/.test(uhd)) release.tags = release.tags.concat('4k');
|
||||
|
||||
release.photos = query.all('.bxslider_pics img').map(el => el.dataset.original || el.src);
|
||||
release.poster = query.poster();
|
||||
release.photos = qa('.bxslider_pics img').map(el => el.dataset.original || el.src);
|
||||
release.poster = qposter();
|
||||
|
||||
const trailer = query.trailer();
|
||||
const trailer = qtrailer();
|
||||
if (trailer) release.trailer = { src: trailer };
|
||||
|
||||
if (release.photos.length > 0) release.channel = await extractChannelFromPhoto(release.photos[0], site);
|
||||
if (release.photos.length > 0) release.channel = await extractChannelFromPhoto(release.photos[0], metaSiteSlugs);
|
||||
|
||||
if (release.channel) {
|
||||
const { pathname } = new URL(url);
|
||||
|
@ -109,23 +124,23 @@ async function scrapeScene({ query }, site, url) {
|
|||
|
||||
async function fetchLatest(site, page = 1) {
|
||||
const url = `${site.url}/movies/page-${page}`;
|
||||
const res = await qu.getAll(url, '#content-main [class^="item"]');
|
||||
const res = await http.get(url);
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeLatest(res.items, site);
|
||||
if (res.statusCode === 200) {
|
||||
return scrapeLatest(res.body.toString(), site);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
return [];
|
||||
}
|
||||
|
||||
async function fetchScene(url, channel) {
|
||||
const res = await qu.get(url);
|
||||
async function fetchScene(url, site, release) {
|
||||
const res = await http.get(url);
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeScene(res.item, channel, url);
|
||||
if (res.statusCode === 200) {
|
||||
return scrapeScene(res.body.toString(), site, url, release?.meta.siteSlugs);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
return [];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -47,6 +47,7 @@ const mikeadriano = require('./mikeadriano');
|
|||
const milehighmedia = require('./milehighmedia');
|
||||
const mindgeek = require('./mindgeek');
|
||||
const mofos = require('./mofos');
|
||||
const naturals = require('./21naturals');
|
||||
const naughtyamerica = require('./naughtyamerica');
|
||||
const newsensations = require('./newsensations');
|
||||
const nubiles = require('./nubiles');
|
||||
|
@ -61,6 +62,8 @@ const privateNetwork = require('./private'); // reserved keyword
|
|||
const puretaboo = require('./puretaboo');
|
||||
const realitykings = require('./realitykings');
|
||||
const score = require('./score');
|
||||
const sextreme = require('./21sextreme');
|
||||
const sextury = require('./21sextury');
|
||||
const teamskeet = require('./teamskeet');
|
||||
const teencoreclub = require('./teencoreclub');
|
||||
const topwebmodels = require('./topwebmodels');
|
||||
|
@ -82,6 +85,9 @@ const freeones = require('./freeones');
|
|||
|
||||
const scrapers = {
|
||||
releases: {
|
||||
'21naturals': naturals,
|
||||
'21sextreme': sextreme,
|
||||
'21sextury': sextury,
|
||||
adulttime,
|
||||
amateurallure,
|
||||
americanpornstar,
|
||||
|
@ -170,7 +176,7 @@ const scrapers = {
|
|||
zerotolerance,
|
||||
},
|
||||
actors: {
|
||||
'21sextury': gamma,
|
||||
'21sextury': sextury,
|
||||
allanal: mikeadriano,
|
||||
amateureuro: porndoe,
|
||||
americanpornstar,
|
||||
|
|
|
@ -42,6 +42,39 @@ function getAvatarFallbacks(avatar) {
|
|||
.flat();
|
||||
}
|
||||
|
||||
/*
|
||||
async function getTrailerLegacy(scene, site, url) {
|
||||
const qualities = [360, 480, 720, 1080, 2160];
|
||||
|
||||
const tokenRes = await http.post(`${site.url}/api/__record_tknreq`, {
|
||||
file: scene.previewVideoUrl1080P,
|
||||
sizes: qualities.join('+'),
|
||||
type: 'trailer',
|
||||
}, {
|
||||
headers: {
|
||||
referer: url,
|
||||
origin: site.url,
|
||||
},
|
||||
});
|
||||
|
||||
if (!tokenRes.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trailerUrl = `${site.url}/api${tokenRes.body.data.url}`;
|
||||
const trailersRes = await http.post(trailerUrl, null, { headers: { referer: url } });
|
||||
|
||||
if (trailersRes.ok) {
|
||||
return qualities.map(quality => (trailersRes.body[quality] ? {
|
||||
src: trailersRes.body[quality].token,
|
||||
quality,
|
||||
} : null)).filter(Boolean);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
|
||||
async function getTrailer(scene, channel, url) {
|
||||
const res = await http.post(`${channel.url}/graphql`, {
|
||||
operationName: 'getToken',
|
||||
|
|
|
@ -205,8 +205,6 @@ async function scrapeChannelReleases(scraper, channelEntity, preData) {
|
|||
}
|
||||
|
||||
async function scrapeChannel(channelEntity, accNetworkReleases) {
|
||||
console.log(channelEntity);
|
||||
|
||||
const scraper = scrapers.releases[channelEntity.slug]
|
||||
|| scrapers.releases[channelEntity.parent?.slug]
|
||||
|| scrapers.releases[channelEntity.parent?.parent?.slug];
|
||||
|
|