Compare commits

..

No commits in common. "2068202ca60891e4d3574fc41b125b1993e6b6b9" and "885f51943a2484ce99019c8983431ca0454a727b" have entirely different histories.

26 changed files with 61 additions and 2666 deletions

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.83.3", "version": "1.83.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.83.3", "version": "1.83.2",
"description": "All the latest porn releases in one place", "description": "All the latest porn releases in one place",
"main": "src/app.js", "main": "src/app.js",
"scripts": { "scripts": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -8,18 +8,6 @@ const networks = [
url: 'https://www.21sextury.com', 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.', 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.',
}, },
{
slug: '21sextreme',
name: '21Sextreme',
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',
},
{
slug: '21naturals',
name: '21Naturals',
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',
},
{ {
slug: 'adulttime', slug: 'adulttime',
name: 'Adult Time', name: 'Adult Time',

View File

@ -2,74 +2,7 @@ const upsert = require('../src/utils/upsert');
/* eslint-disable max-len */ /* eslint-disable max-len */
const sites = [ const sites = [
// 21SEXTREME // 21Sextury
{
slug: 'grandpasfuckteens',
name: 'Grandpas Fuck Teens',
url: 'https://grandpasfuckteens.21sextreme.com',
network: '21sextreme',
},
{
slug: 'oldyounglesbianlove',
name: 'Old Young Lesbian Love',
url: 'https://oldyounglesbianlove.21sextreme.com',
network: '21sextreme',
parameters: {
scene: 'https://21sextreme.com/en/video',
},
},
{
slug: 'lustygrandmas',
name: 'Lusty Grandmas',
url: 'https://lustygrandmas.21sextreme.com',
network: '21sextreme',
},
{
slug: 'teachmefisting',
name: 'Teach Me Fisting',
url: 'https://teachmefisting.21sextreme.com',
network: '21sextreme',
parameters: {
scene: 'https://21sextreme.com/en/video',
},
},
{
slug: 'zoliboy',
name: 'Zoliboy',
url: 'https://zoliboy.21sextreme.com',
network: '21sextreme',
parameters: {
scene: 'https://21sextreme.com/en/video',
},
},
{
slug: 'mightymistress',
name: 'Mighty Mistress',
url: 'https://mightymistress.21sextreme.com',
network: '21sextreme',
parameters: {
scene: 'https://21sextreme.com/en/video',
},
},
{
slug: 'dominatedgirls',
name: 'Dominated Girls',
url: 'https://dominatedgirls.21sextreme.com',
network: '21sextreme',
parameters: {
scene: 'https://21sextreme.com/en/video',
},
},
{
slug: 'homepornreality',
name: 'Home Porn Reality',
url: 'https://homepornreality.21sextreme.com',
network: '21sextreme',
parameters: {
scene: 'https://21sextreme.com/en/video',
},
},
// 21SEXTURY
{ {
slug: 'analteenangels', slug: 'analteenangels',
name: 'Anal Teen Angels', name: 'Anal Teen Angels',

View File

@ -12,14 +12,6 @@ const { scrapeRelease } = require('./scrape-releases');
const { storeReleases } = require('./releases'); const { storeReleases } = require('./releases');
function getAfterDate() { function getAfterDate() {
if (/\d{2,4}-\d{2}-\d{2,4}/.test(argv.after)) {
// using date
return moment
.utc(argv.after, ['YYYY-MM-DD', 'DD-MM-YYYY'])
.toDate();
}
// using time distance (e.g. "1 month")
return moment return moment
.utc() .utc()
.subtract(...argv.after.split(' ')) .subtract(...argv.after.split(' '))
@ -91,17 +83,11 @@ async function deepFetchReleases(baseReleases) {
try { try {
const fullRelease = await scrapeRelease(release.url, release, 'scene'); const fullRelease = await scrapeRelease(release.url, release, 'scene');
if (fullRelease) { return {
return { ...release,
...release, ...fullRelease,
...fullRelease, deep: true,
deep: true, };
};
}
logger.warn(`Release scraper returned empty result for ${release.url}`);
return release;
} catch (error) { } catch (error) {
logger.error(error.stack); logger.error(error.stack);

View File

@ -1,10 +0,0 @@
'use strict';
const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma');
module.exports = {
fetchLatest: fetchApiLatest,
fetchProfile: fetchApiProfile,
fetchUpcoming: fetchApiUpcoming,
fetchScene,
};

View File

@ -1,10 +0,0 @@
'use strict';
const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma');
module.exports = {
fetchLatest: fetchApiLatest,
fetchProfile: fetchApiProfile,
fetchUpcoming: fetchApiUpcoming,
fetchScene,
};

View File

@ -6,8 +6,6 @@ const cheerio = require('cheerio');
const { JSDOM } = require('jsdom'); const { JSDOM } = require('jsdom');
const moment = require('moment'); const moment = require('moment');
const { get, ex } = require('../utils/q');
const slugify = require('../utils/slugify');
const { heightToCm, lbsToKg } = require('../utils/convert'); const { heightToCm, lbsToKg } = require('../utils/convert');
const hairMap = { const hairMap = {
@ -17,7 +15,7 @@ const hairMap = {
Redhead: 'red', Redhead: 'red',
}; };
function scrapeAll(html, site, upcoming) { function scrape(html, site, upcoming) {
const $ = cheerio.load(html, { normalizeWhitespace: true }); const $ = cheerio.load(html, { normalizeWhitespace: true });
const sceneElements = $('.release-card.scene').toArray(); const sceneElements = $('.release-card.scene').toArray();
@ -43,8 +41,6 @@ function scrapeAll(html, site, upcoming) {
const poster = `https:${$(element).find('.card-main-img').attr('data-src')}`; const poster = `https:${$(element).find('.card-main-img').attr('data-src')}`;
const photos = $(element).find('.card-overlay .image-under').map((photoIndex, photoElement) => `https:${$(photoElement).attr('data-src')}`).toArray(); const photos = $(element).find('.card-overlay .image-under').map((photoIndex, photoElement) => `https:${$(photoElement).attr('data-src')}`).toArray();
const channel = slugify($(element).find('.collection').attr('title'), { delimiter: '' });
return acc.concat({ return acc.concat({
url, url,
entryId, entryId,
@ -57,55 +53,66 @@ function scrapeAll(html, site, upcoming) {
likes, likes,
dislikes, dislikes,
}, },
channel,
site, site,
}); });
}, []); }, []);
} }
async function scrapeScene(html, url, _site) { async function scrapeScene(html, url, site) {
const $ = cheerio.load(html, { normalizeWhitespace: true }); const $ = cheerio.load(html, { normalizeWhitespace: true });
const release = {};
const videoJson = $('script:contains("window.videoUiOptions")').html(); const videoJson = $('script:contains("window.videoUiOptions")').html();
const videoString = videoJson.slice(videoJson.indexOf('{"stream_info":'), videoJson.lastIndexOf('},') + 1); const videoData = JSON.parse(videoJson.slice(videoJson.indexOf('{"stream_info":'), videoJson.lastIndexOf('"},') + 2));
const videoData = JSON.parse(videoString);
[release.entryId] = url.split('/').slice(-3, -2); const entryId = url.split('/').slice(-3, -2)[0];
release.title = $('.scene-title[itemprop="name"]').text(); const title = $('.scene-title[itemprop="name"]').text();
release.description = $('#scene-description p[itemprop="description"]') const description = $('#scene-description p[itemprop="description"]')
.contents() .contents()
.first() .first()
.text() .text()
.trim(); .trim();
release.date = moment.utc($('.more-scene-info .scene-date').text(), 'MMMM DD, YYYY').toDate(); const date = moment.utc($('.more-scene-info .scene-date').text(), 'MMMM DD, YYYY').toDate();
release.actors = $('.related-model a').map((actorIndex, actorElement) => $(actorElement).text()).toArray(); const actors = $('.related-model a').map((actorIndex, actorElement) => $(actorElement).text()).toArray();
release.duration = Number($('.scene-length[itemprop="duration"]').attr('content').slice(1, -1)) * 60; const duration = Number($('.scene-length[itemprop="duration"]').attr('content').slice(1, -1)) * 60;
release.likes = Number($('.label-rating .like').text()); const likes = Number($('.label-rating .like').text());
release.dislikes = Number($('.label-rating .dislike').text()); const dislikes = Number($('.label-rating .dislike').text());
const siteElement = $('.niche-site-logo'); const siteElement = $('.niche-site-logo');
// const siteUrl = `https://www.brazzers.com${siteElement.attr('href').slice(0, -1)}`; // const siteUrl = `https://www.brazzers.com${siteElement.attr('href').slice(0, -1)}`;
const siteName = siteElement.attr('title'); const siteName = siteElement.attr('title');
release.channel = siteName.replace(/\s+/g, '').toLowerCase(); const channel = siteName.replace(/\s+/g, '').toLowerCase();
release.tags = $('.tag-card-container a').map((tagIndex, tagElement) => $(tagElement).text()).toArray(); const tags = $('.tag-card-container a').map((tagIndex, tagElement) => $(tagElement).text()).toArray();
release.photos = $('.carousel-thumb a').map((photoIndex, photoElement) => `https:${$(photoElement).attr('href')}`).toArray();
const posterPath = videoData?.poster || $('meta[itemprop="thumbnailUrl"]').attr('content') || $('#trailer-player-container').attr('data-player-img'); const poster = `https:${videoData.poster}`;
if (posterPath) release.poster = `https:${posterPath}`; const photos = $('.carousel-thumb a').map((photoIndex, photoElement) => `https:${$(photoElement).attr('href')}`).toArray();
if (videoData) { const trailer = Object.entries(videoData.stream_info.http.paths).map(([quality, path]) => ({
release.trailer = Object.entries(videoData.stream_info.http.paths).map(([quality, path]) => ({ src: `https:${path}`,
src: `https:${path}`, quality: Number(quality.match(/\d{3,}/)[0]),
quality: Number(quality.match(/\d{3,}/)[0]), }));
}));
}
return release; return {
url,
entryId,
title,
description,
actors,
date,
poster,
photos,
trailer,
duration,
rating: {
likes,
dislikes,
},
tags,
site,
channel,
};
} }
function scrapeActorSearch(html, url, actorName) { function scrapeActorSearch(html, url, actorName) {
@ -115,26 +122,13 @@ function scrapeActorSearch(html, url, actorName) {
return actorLink ? actorLink.href : null; return actorLink ? actorLink.href : null;
} }
async function fetchActorReleases({ qu, html }, accReleases = []) { function scrapeProfile(html, url, actorName) {
const releases = scrapeAll(html); const { document } = new JSDOM(html).window;
const next = qu('.pagination .next a');
if (next) { const avatarEl = document.querySelector('.big-pic-model-container img');
const url = `https://www.brazzers.com${next}`; const descriptionEl = document.querySelector('.model-profile-specs p');
const qNext = await get(url); const bioKeys = Array.from(document.querySelectorAll('.profile-spec-list label'), el => el.textContent.replace(/\n+|\s{2,}/g, '').trim());
const bioValues = Array.from(document.querySelectorAll('.profile-spec-list var'), el => el.textContent.replace(/\n+|\s{2,}/g, '').trim());
return fetchActorReleases(qNext, accReleases.concat(releases));
}
return accReleases.concat(releases);
}
async function scrapeProfile(html, url, actorName) {
const qProfile = ex(html);
const { q, qa } = qProfile;
const bioKeys = qa('.profile-spec-list label', true).map(key => key.replace(/\n+|\s{2,}/g, '').trim());
const bioValues = qa('.profile-spec-list var', true).map(value => value.replace(/\n+|\s{2,}/g, '').trim());
const bio = bioKeys.reduce((acc, key, index) => ({ ...acc, [key]: bioValues[index] }), {}); const bio = bioKeys.reduce((acc, key, index) => ({ ...acc, [key]: bioValues[index] }), {});
@ -142,8 +136,6 @@ async function scrapeProfile(html, url, actorName) {
name: actorName, name: actorName,
}; };
profile.description = q('.model-profile-specs p', true);
if (bio.Ethnicity) profile.ethnicity = bio.Ethnicity; if (bio.Ethnicity) profile.ethnicity = bio.Ethnicity;
if (bio.Measurements && bio.Measurements.match(/\d+[A-Z]+-\d+-\d+/)) [profile.bust, profile.waist, profile.hip] = bio.Measurements.split('-'); if (bio.Measurements && bio.Measurements.match(/\d+[A-Z]+-\d+-\d+/)) [profile.bust, profile.waist, profile.hip] = bio.Measurements.split('-');
if (bio['Date of Birth'] && bio['Date of Birth'] !== 'Unknown') profile.birthdate = moment.utc(bio['Date of Birth'], 'MMMM DD, YYYY').toDate(); if (bio['Date of Birth'] && bio['Date of Birth'] !== 'Unknown') profile.birthdate = moment.utc(bio['Date of Birth'], 'MMMM DD, YYYY').toDate();
@ -160,10 +152,10 @@ async function scrapeProfile(html, url, actorName) {
if (bio['Body Art'] && bio['Body Art'].match('Tattoo')) profile.hasTattoos = true; if (bio['Body Art'] && bio['Body Art'].match('Tattoo')) profile.hasTattoos = true;
if (bio['Body Art'] && bio['Body Art'].match('Piercing')) profile.hasPiercings = true; if (bio['Body Art'] && bio['Body Art'].match('Piercing')) profile.hasPiercings = true;
const avatarEl = q('.big-pic-model-container img'); if (descriptionEl) profile.description = descriptionEl.textContent.trim();
if (avatarEl) profile.avatar = `https:${avatarEl.src}`; if (avatarEl) profile.avatar = `https:${avatarEl.src}`;
profile.releases = await fetchActorReleases(qProfile); profile.releases = Array.from(document.querySelectorAll('.release-card-container .scene-card-title a'), el => `https://brazzers.com${el.href}`);
return profile; return profile;
} }
@ -171,13 +163,13 @@ async function scrapeProfile(html, url, actorName) {
async function fetchLatest(site, page = 1) { async function fetchLatest(site, page = 1) {
const res = await bhttp.get(`${site.url}/page/${page}/`); const res = await bhttp.get(`${site.url}/page/${page}/`);
return scrapeAll(res.body.toString(), site, false); return scrape(res.body.toString(), site, false);
} }
async function fetchUpcoming(site) { async function fetchUpcoming(site) {
const res = await bhttp.get(`${site.url}/`); const res = await bhttp.get(`${site.url}/`);
return scrapeAll(res.body.toString(), site, true); return scrape(res.body.toString(), site, true);
} }
async function fetchScene(url, site) { async function fetchScene(url, site) {

View File

@ -186,7 +186,7 @@ async function scrapeScene(html, url, site) {
// date in data object is not the release date of the scene, but the date the entry was added; only use as fallback // date in data object is not the release date of the scene, but the date the entry was added; only use as fallback
const dateString = $('.updatedDate').first().text().trim(); const dateString = $('.updatedDate').first().text().trim();
const dateMatch = dateString.match(/\d{2,4}[-/]\d{2}[-/]\d{2,4}/)?.[0]; const dateMatch = dateString.match(/\d{2,4}-\d{2}-\d{2,4}/)?.[0];
if (dateMatch) release.date = moment.utc(dateMatch, ['MM-DD-YYYY', 'YYYY-MM-DD']).toDate(); if (dateMatch) release.date = moment.utc(dateMatch, ['MM-DD-YYYY', 'YYYY-MM-DD']).toDate();
else if (data?.dateCreated) release.date = moment.utc(data.dateCreated, 'YYYY-MM-DD').toDate(); else if (data?.dateCreated) release.date = moment.utc(data.dateCreated, 'YYYY-MM-DD').toDate();

View File

@ -16,7 +16,6 @@ const famedigital = require('./famedigital');
const fantasymassage = require('./fantasymassage'); const fantasymassage = require('./fantasymassage');
const freeones = require('./freeones'); const freeones = require('./freeones');
const freeonesLegacy = require('./freeones_legacy'); const freeonesLegacy = require('./freeones_legacy');
const girlsway = require('./girlsway');
const iconmale = require('./iconmale'); const iconmale = require('./iconmale');
const jayrock = require('./jayrock'); const jayrock = require('./jayrock');
const julesjordan = require('./julesjordan'); const julesjordan = require('./julesjordan');
@ -29,7 +28,7 @@ const mikeadriano = require('./mikeadriano');
const milehighmedia = require('./milehighmedia'); const milehighmedia = require('./milehighmedia');
const mindgeek = require('./mindgeek'); const mindgeek = require('./mindgeek');
const mofos = require('./mofos'); const mofos = require('./mofos');
const naturals = require('./21naturals'); const girlsway = require('./girlsway');
const naughtyamerica = require('./naughtyamerica'); const naughtyamerica = require('./naughtyamerica');
const perfectgonzo = require('./perfectgonzo'); const perfectgonzo = require('./perfectgonzo');
const pervcity = require('./pervcity'); const pervcity = require('./pervcity');
@ -39,10 +38,9 @@ const privateNetwork = require('./private'); // reserved keyword
const puretaboo = require('./puretaboo'); const puretaboo = require('./puretaboo');
const realitykings = require('./realitykings'); const realitykings = require('./realitykings');
const score = require('./score'); const score = require('./score');
const sextreme = require('./21sextreme');
const sextury = require('./21sextury');
const teamskeet = require('./teamskeet'); const teamskeet = require('./teamskeet');
const transangels = require('./transangels'); const transangels = require('./transangels');
const twentyonesextury = require('./21sextury');
const twistys = require('./twistys'); const twistys = require('./twistys');
const vixen = require('./vixen'); const vixen = require('./vixen');
const vogov = require('./vogov'); const vogov = require('./vogov');
@ -52,9 +50,7 @@ const xempire = require('./xempire');
module.exports = { module.exports = {
releases: { releases: {
adulttime, adulttime,
'21naturals': naturals, '21sextury': twentyonesextury,
'21sextreme': sextreme,
'21sextury': sextury,
babes, babes,
bang, bang,
bangbros, bangbros,
@ -97,7 +93,7 @@ module.exports = {
}, },
actors: { actors: {
// ordered by data priority // ordered by data priority
'21sextury': sextury, '21sextury': twentyonesextury,
babes, babes,
bangbros, bangbros,
blowpass, blowpass,