Compare commits
2 Commits
6cb990dbdb
...
c85915bf97
| Author | SHA1 | Date |
|---|---|---|
|
|
c85915bf97 | |
|
|
6338e8fb8d |
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "traxxx",
|
"name": "traxxx",
|
||||||
"version": "1.245.12",
|
"version": "1.245.13",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "traxxx",
|
"name": "traxxx",
|
||||||
"version": "1.245.12",
|
"version": "1.245.13",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.458.0",
|
"@aws-sdk/client-s3": "^3.458.0",
|
||||||
|
|
@ -93,7 +93,7 @@
|
||||||
"tunnel": "0.0.6",
|
"tunnel": "0.0.6",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"undici": "^5.28.1",
|
"undici": "^5.28.1",
|
||||||
"unprint": "^0.18.6",
|
"unprint": "^0.18.7",
|
||||||
"url-pattern": "^1.0.3",
|
"url-pattern": "^1.0.3",
|
||||||
"v-tooltip": "^2.1.3",
|
"v-tooltip": "^2.1.3",
|
||||||
"video.js": "^8.6.1",
|
"video.js": "^8.6.1",
|
||||||
|
|
@ -20340,9 +20340,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unprint": {
|
"node_modules/unprint": {
|
||||||
"version": "0.18.6",
|
"version": "0.18.7",
|
||||||
"resolved": "https://registry.npmjs.org/unprint/-/unprint-0.18.6.tgz",
|
"resolved": "https://registry.npmjs.org/unprint/-/unprint-0.18.7.tgz",
|
||||||
"integrity": "sha512-kcDpsaTaMrxY0AkoHq1bGPuVz6Cv1umC0kA1U58Th+UhFarmwPB5racAY514eWEpjC9AXGEhOvIa+n2hErQmRg==",
|
"integrity": "sha512-Su7SQ7UxvM5SHuvkLmSGMiWi+nS6Qh3489qqbPYaqvH9DFBDc5C4544mGyysU/7ZKO+5RtAi59prVTNPo8rlSw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bottleneck": "^2.19.5",
|
"bottleneck": "^2.19.5",
|
||||||
"cookie": "^1.1.1",
|
"cookie": "^1.1.1",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "traxxx",
|
"name": "traxxx",
|
||||||
"version": "1.245.12",
|
"version": "1.245.13",
|
||||||
"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": {
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
"tunnel": "0.0.6",
|
"tunnel": "0.0.6",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"undici": "^5.28.1",
|
"undici": "^5.28.1",
|
||||||
"unprint": "^0.18.6",
|
"unprint": "^0.18.7",
|
||||||
"url-pattern": "^1.0.3",
|
"url-pattern": "^1.0.3",
|
||||||
"v-tooltip": "^2.1.3",
|
"v-tooltip": "^2.1.3",
|
||||||
"video.js": "^8.6.1",
|
"video.js": "^8.6.1",
|
||||||
|
|
|
||||||
|
|
@ -818,9 +818,6 @@ const sites = [
|
||||||
slug: 'archangel',
|
slug: 'archangel',
|
||||||
name: 'ArchAngel',
|
name: 'ArchAngel',
|
||||||
url: 'https://www.archangelvideo.com',
|
url: 'https://www.archangelvideo.com',
|
||||||
parameters: {
|
|
||||||
path: '/tour',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// ASSYLUM
|
// ASSYLUM
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,171 +1,35 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ALSO USED BY THE FLOURISH
|
|
||||||
|
|
||||||
const unprint = require('unprint');
|
const unprint = require('unprint');
|
||||||
|
|
||||||
const slugify = require('../utils/slugify');
|
const slugify = require('../utils/slugify');
|
||||||
const { feetInchesToCm } = require('../utils/convert');
|
const { convert } = require('../utils/convert');
|
||||||
|
const tryUrls = require('../utils/try-urls');
|
||||||
|
|
||||||
const placeholder = /images\/p\d+\.jpe?g/i;
|
function scrapeAll(scenes, channel) {
|
||||||
|
|
||||||
function getEntryId(release) {
|
|
||||||
return slugify(new URL(release.url).pathname.match(/\/([\w-]+)\.html/)?.[1]
|
|
||||||
|| [unprint.formatDate(release.date, 'YYYY-MM-DD'), release.title, ...release.actors]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrapeAll(scenes) {
|
|
||||||
return scenes.map(({ query }) => {
|
return scenes.map(({ query }) => {
|
||||||
const release = {};
|
const release = {};
|
||||||
|
|
||||||
release.url = query.url('a');
|
release.url = query.url('a');
|
||||||
|
release.entryId = slugify(new URL(release.url).pathname.match(/trailers\/(.*)/)[1]);
|
||||||
|
|
||||||
release.title = query.content('a span');
|
release.title = query.content('h2 a');
|
||||||
|
|
||||||
release.date = query.date('.timeDate', 'YYYY-MM-DD');
|
|
||||||
release.duration = query.duration('.timeDate');
|
|
||||||
|
|
||||||
release.actors = query.all('a[href*="models/"], a[href*="sets.php"]').map((actorEl) => ({
|
release.actors = query.all('a[href*="models/"], a[href*="sets.php"]').map((actorEl) => ({
|
||||||
name: unprint.query.content(actorEl),
|
name: unprint.query.content(actorEl),
|
||||||
url: unprint.query.url(actorEl, null),
|
url: unprint.query.url(actorEl, null, { origin: channel.url }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const poster = query.img('img.mainThumb');
|
release.poster = query.img('.thumbnail img');
|
||||||
const previewCount = query.number('img.mainThumb', { attribute: 'cnt' });
|
release.teaser = query.video('.thumbnail img', { attribute: 'data-vid' }); // not a mistake, video source is on img tag
|
||||||
|
|
||||||
if (poster && !placeholder.test(poster)) {
|
|
||||||
const posterFallbacks = [
|
|
||||||
poster.replace('-1x', '-3x'),
|
|
||||||
poster.replace('-1x', '-2x'),
|
|
||||||
poster.replace('-1x', '-4x'),
|
|
||||||
poster,
|
|
||||||
];
|
|
||||||
|
|
||||||
release.poster = posterFallbacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previewCount) {
|
|
||||||
release.photos = Array.from(
|
|
||||||
{ length: previewCount - 1 },
|
|
||||||
(value, index) => [3, 2, 4, 1].map((scale) => unprint.prefixUrl(query.img('img.mainThumb', { attribute: `src${index + 1}_${scale}x` }))).filter(Boolean), // 4x is unnecessarily big and possibly upscaled
|
|
||||||
).filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
release.photoCount = query.number('.timeDate', { match: /(\d+) photos/i, matchIndex: 1 });
|
|
||||||
|
|
||||||
release.entryId = getEntryId(release);
|
|
||||||
|
|
||||||
return release;
|
return release;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrapeScene({ query, html }, { url, entity, baseRelease }) {
|
async function fetchLatest(channel, page = 1) {
|
||||||
const release = { url };
|
const url = `${channel.url}/porn-categories/movies/?sort=most-recent&page=${page}`;
|
||||||
|
const res = await unprint.get(url, { selectAll: '.content div[data-setid]' });
|
||||||
release.title = query.content('.title h2');
|
|
||||||
release.description = query.content('.description p');
|
|
||||||
|
|
||||||
release.date = query.date('.info p', 'MMMM D, YYYY');
|
|
||||||
release.duration = query.duration('.info p');
|
|
||||||
|
|
||||||
release.actors = query.all('.info a[href*="models/"], .info a[href*="sets.php"]').map((actorEl) => ({
|
|
||||||
name: unprint.query.content(actorEl),
|
|
||||||
url: unprint.query.url(actorEl, null),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const poster = unprint.prefixUrl(query.img('.update_thumb') || html.match(/poster="(.*\.jpg)"/)?.[1], entity.url);
|
|
||||||
|
|
||||||
if (poster && !placeholder.test(poster)) {
|
|
||||||
const posterFallbacks = [
|
|
||||||
poster.replace('-1x', '-3x'),
|
|
||||||
poster.replace('-1x', '-2x'),
|
|
||||||
poster.replace('-1x', '-4x'),
|
|
||||||
poster,
|
|
||||||
];
|
|
||||||
|
|
||||||
// scene page poster usually different from overview page, don't replace
|
|
||||||
if (baseRelease?.poster && baseRelease.poster !== poster) {
|
|
||||||
release.photos = baseRelease.photos
|
|
||||||
? [posterFallbacks, ...baseRelease.photos]
|
|
||||||
: [posterFallbacks];
|
|
||||||
} else {
|
|
||||||
release.poster = posterFallbacks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const trailer = html.match(/src="(.*\.mp4)"/)?.[1];
|
|
||||||
|
|
||||||
if (trailer) {
|
|
||||||
release.trailer = unprint.prefixUrl(encodeURI(trailer), entity.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
release.tags = query.contents('.info .tags a');
|
|
||||||
|
|
||||||
release.photoCount = query.number('.info', { match: /(\d+) photos/i, matchIndex: 1 });
|
|
||||||
release.entryId = getEntryId(release);
|
|
||||||
|
|
||||||
return release;
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrapeMovie({ query, element }, { entity, url }) {
|
|
||||||
const release = { url };
|
|
||||||
|
|
||||||
release.title = query.content('.title h2');
|
|
||||||
release.description = query.content('.aboutArea p');
|
|
||||||
|
|
||||||
release.covers = [[
|
|
||||||
query.img('.update_thumb', { attribute: 'src0_2x', origin: entity.url }),
|
|
||||||
query.img('.update_thumb', { attribute: 'src0_1x', origin: entity.url }),
|
|
||||||
query.img('.update_thumb', { attribute: 'src0', origin: entity.url }),
|
|
||||||
// usually upscaled
|
|
||||||
query.img('.update_thumb', { attribute: 'src0_4x', origin: entity.url }),
|
|
||||||
query.img('.update_thumb', { attribute: 'src0_3x', origin: entity.url }),
|
|
||||||
].filter(Boolean)];
|
|
||||||
|
|
||||||
release.entryId = getEntryId(release);
|
|
||||||
|
|
||||||
release.scenes = scrapeAll(unprint.initAll(element, '.item-video'));
|
|
||||||
|
|
||||||
return release;
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrapeProfile({ query, element }, { url, entity }) {
|
|
||||||
const profile = { url };
|
|
||||||
|
|
||||||
const bio = Object.fromEntries(query.all('.stats li')
|
|
||||||
.map((row) => [
|
|
||||||
slugify(unprint.query.content(row, '.data-name, span'), '_'),
|
|
||||||
unprint.query.text(row),
|
|
||||||
])
|
|
||||||
.filter(([key, value]) => key && value));
|
|
||||||
|
|
||||||
profile.description = query.content('.aboutArea p');
|
|
||||||
|
|
||||||
profile.birthPlace = bio.place_of_birth;
|
|
||||||
profile.dateOfBirth = unprint.extractDate(bio.age, 'MMMM D, YYYY');
|
|
||||||
|
|
||||||
profile.height = Number(bio.height?.match(/(\d+)\s*cm/)?.[1]) || (/\d fe*t \d+ inch/i.test(bio.height) && feetInchesToCm(bio.height)) || null;
|
|
||||||
profile.measurements = bio.measurements;
|
|
||||||
|
|
||||||
profile.hairColor = bio.hair_color;
|
|
||||||
profile.eyes = bio.eye_color;
|
|
||||||
|
|
||||||
profile.avatar = [
|
|
||||||
query.img('.model_bio_thumb', { attribute: 'src0_4x', origin: entity.url }),
|
|
||||||
query.img('.model_bio_thumb', { attribute: 'src0_3x', origin: entity.url }),
|
|
||||||
query.img('.model_bio_thumb', { attribute: 'src0_2x', origin: entity.url }),
|
|
||||||
query.img('.model_bio_thumb', { attribute: 'src0_1x', origin: entity.url }),
|
|
||||||
query.img('.model_bio_thumb', { attribute: 'src0', origin: entity.url }),
|
|
||||||
].filter((avatar) => avatar && !placeholder.test(avatar));
|
|
||||||
|
|
||||||
profile.scenes = scrapeAll(unprint.initAll(element, '.item-video'));
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchLatest(channel, page = 1, context) {
|
|
||||||
const url = `${channel.url}${context.parameters.path || ''}/categories/movies_${page}_d.html`;
|
|
||||||
const res = await unprint.get(url, { selectAll: '.item-video' });
|
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return scrapeAll(res.context, channel);
|
return scrapeAll(res.context, channel);
|
||||||
|
|
@ -174,32 +38,63 @@ async function fetchLatest(channel, page = 1, context) {
|
||||||
return res.status;
|
return res.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchProfile({ name: actorName, url: actorUrl }, { entity, include, parameters }) {
|
function scrapeScene({ query }, { url }) {
|
||||||
const res = await [
|
const release = {};
|
||||||
|
|
||||||
|
release.entryId = slugify(new URL(url).pathname.match(/trailers\/(.*)/)[1]);
|
||||||
|
|
||||||
|
release.title = query.content('h1.title_bar');
|
||||||
|
release.description = query.content('.description-text');
|
||||||
|
|
||||||
|
release.date = query.date('//label[contains(text(), \'Date\')]/following-sibling::p[1]', 'YYYY-MM-DD');
|
||||||
|
|
||||||
|
release.actors = query.all('.text a[href*="/models"]').map((actorEl) => ({
|
||||||
|
name: unprint.query.content(actorEl),
|
||||||
|
url: unprint.query.url(actorEl, null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
release.tags = query.contents('.text a[href*="categories/"]');
|
||||||
|
|
||||||
|
release.poster = query.poster('#preview video');
|
||||||
|
release.trailer = query.video('#preview video source');
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeProfile({ query }, { url }) {
|
||||||
|
const profile = { url };
|
||||||
|
|
||||||
|
const bio = Object.fromEntries(query.all('.model-details > div').map((bioEl) => [
|
||||||
|
slugify(unprint.query.content(bioEl, 'h2'), '_'),
|
||||||
|
unprint.query.text(bioEl),
|
||||||
|
]));
|
||||||
|
|
||||||
|
profile.avatar = [
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_3x' }),
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_2x' }),
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_1x' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
profile.description = [query.content('.model-bio-text'), bio.funfact].filter(Boolean).join(' ');
|
||||||
|
profile.aliases = bio.alias?.split(/[,\n]/).map((alias) => alias.trim());
|
||||||
|
|
||||||
|
profile.age = parseInt(bio.age, 10) || null;
|
||||||
|
profile.dateOfBirth = unprint.extractDate(bio.age, 'MM/DD/YYYY');
|
||||||
|
profile.measurements = bio.measurements;
|
||||||
|
profile.height = Number(bio.height.match(/(\d+)\s*cm/)?.[1]) || convert(bio.height, 'cm');
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile({ name: actorName, url: actorUrl }, { entity, include }) {
|
||||||
|
const { res, url } = await tryUrls([
|
||||||
actorUrl,
|
actorUrl,
|
||||||
`${entity.url}${parameters.path || ''}/models/${slugify(actorName, '-')}.html`,
|
`${entity.url}/models/${slugify(actorName, '')}.html`,
|
||||||
`${entity.url}${parameters.path || ''}/models/${slugify(actorName, '')}.html`,
|
`${entity.url}/models/${slugify(actorName, '-')}.html`,
|
||||||
].reduce(async (chain, url) => {
|
]);
|
||||||
const prevRes = await chain;
|
|
||||||
|
|
||||||
if (prevRes.ok || !url) {
|
|
||||||
return prevRes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actorRes = await unprint.get(url);
|
|
||||||
|
|
||||||
if (actorRes.ok) {
|
|
||||||
return {
|
|
||||||
...actorRes,
|
|
||||||
url,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return prevRes;
|
|
||||||
}, Promise.resolve({ ok: false, status: null }));
|
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return scrapeProfile(res.context, { entity, include, url: res.url });
|
return scrapeProfile(res.context, { entity, include, url });
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status;
|
return res.status;
|
||||||
|
|
@ -209,5 +104,4 @@ module.exports = {
|
||||||
fetchLatest,
|
fetchLatest,
|
||||||
fetchProfile,
|
fetchProfile,
|
||||||
scrapeScene,
|
scrapeScene,
|
||||||
scrapeMovie,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const unprint = require('unprint');
|
||||||
|
|
||||||
const slugify = require('../utils/slugify');
|
const slugify = require('../utils/slugify');
|
||||||
const { convert } = require('../utils/convert');
|
const { convert } = require('../utils/convert');
|
||||||
|
const tryUrls = require('../utils/try-urls');
|
||||||
|
|
||||||
const channelCodes = {
|
const channelCodes = {
|
||||||
ao: 'analoverdose',
|
ao: 'analoverdose',
|
||||||
|
|
@ -151,36 +152,17 @@ function scrapeProfile({ query }, url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchProfile({ name: actorName, url: actorUrl }) {
|
async function fetchProfile({ name: actorName, url: actorUrl }) {
|
||||||
if (actorUrl) {
|
const { res, url } = await tryUrls([
|
||||||
const res = await unprint.get(actorUrl);
|
actorUrl,
|
||||||
|
`https://pervcity.com/models/${slugify(actorName)}.html`,
|
||||||
|
`https://pervcity.com/models/${slugify(actorName, '')}.html`,
|
||||||
|
]);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return scrapeProfile(res.context);
|
return scrapeProfile(res.context, url);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `https://pervcity.com/models/${slugify(actorName)}.html`;
|
return res.status;
|
||||||
const url2 = `https://pervcity.com/models/${slugify(actorName, '')}.html`;
|
|
||||||
|
|
||||||
if (url !== actorUrl) {
|
|
||||||
const res = await unprint.get(url);
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return scrapeProfile(res.context, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url2 !== actorUrl) {
|
|
||||||
const res = await unprint.get(url2);
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return scrapeProfile(res.context, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ const teamskeet = require('./teamskeet');
|
||||||
const teencoreclub = require('./teencoreclub');
|
const teencoreclub = require('./teencoreclub');
|
||||||
const teenmegaworld = require('./teenmegaworld');
|
const teenmegaworld = require('./teenmegaworld');
|
||||||
const testedefudelidade = require('./testedefudelidade');
|
const testedefudelidade = require('./testedefudelidade');
|
||||||
|
const theflourish = require('./theflourish');
|
||||||
const tokyohot = require('./tokyohot');
|
const tokyohot = require('./tokyohot');
|
||||||
// const topwebmodels = require('./topwebmodels');
|
// const topwebmodels = require('./topwebmodels');
|
||||||
const traxxx = require('./traxxx');
|
const traxxx = require('./traxxx');
|
||||||
|
|
@ -92,13 +93,14 @@ const scrapers = {
|
||||||
// gamma
|
// gamma
|
||||||
gamma,
|
gamma,
|
||||||
// daringsex,
|
// daringsex,
|
||||||
|
// arch angel
|
||||||
|
archangel,
|
||||||
// etc
|
// etc
|
||||||
amateurallure,
|
amateurallure,
|
||||||
americanpornstar,
|
americanpornstar,
|
||||||
amateureuro: porndoe,
|
amateureuro: porndoe,
|
||||||
amnesiac,
|
amnesiac,
|
||||||
angelogodshackoriginal,
|
angelogodshackoriginal,
|
||||||
archangel,
|
|
||||||
asiam: modelmedia,
|
asiam: modelmedia,
|
||||||
assylum,
|
assylum,
|
||||||
aziani,
|
aziani,
|
||||||
|
|
@ -177,11 +179,11 @@ const scrapers = {
|
||||||
snowvalley,
|
snowvalley,
|
||||||
spizoo,
|
spizoo,
|
||||||
swallowsalon: julesjordan,
|
swallowsalon: julesjordan,
|
||||||
theflourish: archangel,
|
|
||||||
teencoreclub,
|
teencoreclub,
|
||||||
teenmegaworld,
|
teenmegaworld,
|
||||||
teamskeet,
|
teamskeet,
|
||||||
testedefudelidade,
|
testedefudelidade,
|
||||||
|
theflourish,
|
||||||
tokyohot,
|
tokyohot,
|
||||||
transbella: porndoe,
|
transbella: porndoe,
|
||||||
traxxx,
|
traxxx,
|
||||||
|
|
@ -254,20 +256,27 @@ const scrapers = {
|
||||||
'3rddegreefilms': gamma,
|
'3rddegreefilms': gamma,
|
||||||
addicted2girls: gamma,
|
addicted2girls: gamma,
|
||||||
genderxfilms: gamma,
|
genderxfilms: gamma,
|
||||||
|
// mike adriano
|
||||||
|
trueanal: mikeadriano,
|
||||||
|
swallowed: mikeadriano,
|
||||||
|
nympho: mikeadriano,
|
||||||
|
dirtyauditions: mikeadriano,
|
||||||
|
analonly: mikeadriano,
|
||||||
|
allanal: mikeadriano,
|
||||||
|
// the flourish
|
||||||
|
theflourishxxx: theflourish,
|
||||||
// etc
|
// etc
|
||||||
'18vr': badoink,
|
'18vr': badoink,
|
||||||
adultempire,
|
adultempire,
|
||||||
allanal: mikeadriano,
|
archangel,
|
||||||
allherluv: missax,
|
allherluv: missax,
|
||||||
amateureuro: porndoe,
|
amateureuro: porndoe,
|
||||||
americanpornstar,
|
americanpornstar,
|
||||||
analbbc: fullpornnetwork,
|
analbbc: fullpornnetwork,
|
||||||
analized: fullpornnetwork,
|
analized: fullpornnetwork,
|
||||||
analonly: mikeadriano,
|
|
||||||
analviolation: fullpornnetwork,
|
analviolation: fullpornnetwork,
|
||||||
angelogodshackoriginal,
|
angelogodshackoriginal,
|
||||||
anilos: nubiles,
|
anilos: nubiles,
|
||||||
archangel,
|
|
||||||
asiam: modelmedia,
|
asiam: modelmedia,
|
||||||
aziani,
|
aziani,
|
||||||
'2poles1hole': aziani,
|
'2poles1hole': aziani,
|
||||||
|
|
@ -288,7 +297,6 @@ const scrapers = {
|
||||||
cherrypimps,
|
cherrypimps,
|
||||||
cumlouder,
|
cumlouder,
|
||||||
deeplush: nubiles,
|
deeplush: nubiles,
|
||||||
dirtyauditions: mikeadriano,
|
|
||||||
dorcelclub: dorcel,
|
dorcelclub: dorcel,
|
||||||
doubleviewcasting: firstanalquest,
|
doubleviewcasting: firstanalquest,
|
||||||
dtfsluts: fullpornnetwork,
|
dtfsluts: fullpornnetwork,
|
||||||
|
|
@ -338,7 +346,6 @@ const scrapers = {
|
||||||
nfbusty: nubiles,
|
nfbusty: nubiles,
|
||||||
nubilefilms: nubiles,
|
nubilefilms: nubiles,
|
||||||
nubiles,
|
nubiles,
|
||||||
nympho: mikeadriano,
|
|
||||||
onlyprince: fullpornnetwork,
|
onlyprince: fullpornnetwork,
|
||||||
pascalssubsluts,
|
pascalssubsluts,
|
||||||
pervcity,
|
pervcity,
|
||||||
|
|
@ -370,12 +377,6 @@ const scrapers = {
|
||||||
uralesbian: snowvalley,
|
uralesbian: snowvalley,
|
||||||
rawattack: spizoo,
|
rawattack: spizoo,
|
||||||
spizoo,
|
spizoo,
|
||||||
swallowed: mikeadriano,
|
|
||||||
milfcandy: archangel,
|
|
||||||
theflourishamateurs: archangel,
|
|
||||||
theflourishpov: archangel,
|
|
||||||
theflourishfetish: archangel,
|
|
||||||
theflourishxxx: archangel,
|
|
||||||
teamskeet,
|
teamskeet,
|
||||||
teencoreclub,
|
teencoreclub,
|
||||||
teenmegaworld,
|
teenmegaworld,
|
||||||
|
|
@ -384,7 +385,6 @@ const scrapers = {
|
||||||
tokyohot,
|
tokyohot,
|
||||||
transbella: porndoe,
|
transbella: porndoe,
|
||||||
tranzvr: wankzvr,
|
tranzvr: wankzvr,
|
||||||
trueanal: mikeadriano,
|
|
||||||
vipsexvault: porndoe,
|
vipsexvault: porndoe,
|
||||||
virtualtaboo,
|
virtualtaboo,
|
||||||
darkroomvr: virtualtaboo,
|
darkroomvr: virtualtaboo,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ALSO USED BY THE FLOURISH
|
||||||
|
|
||||||
|
const unprint = require('unprint');
|
||||||
|
|
||||||
|
const slugify = require('../utils/slugify');
|
||||||
|
const { feetInchesToCm } = require('../utils/convert');
|
||||||
|
|
||||||
|
const placeholder = /images\/p\d+\.jpe?g/i;
|
||||||
|
|
||||||
|
function getEntryId(release) {
|
||||||
|
return slugify(new URL(release.url).pathname.match(/\/([\w-]+)\.html/)?.[1]
|
||||||
|
|| [unprint.formatDate(release.date, 'YYYY-MM-DD'), release.title, ...release.actors]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeAll(scenes) {
|
||||||
|
return scenes.map(({ query }) => {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.url = query.url('a');
|
||||||
|
|
||||||
|
release.title = query.content('a span');
|
||||||
|
|
||||||
|
release.date = query.date('.timeDate', 'YYYY-MM-DD');
|
||||||
|
release.duration = query.duration('.timeDate');
|
||||||
|
|
||||||
|
release.actors = query.all('a[href*="models/"], a[href*="sets.php"]').map((actorEl) => ({
|
||||||
|
name: unprint.query.content(actorEl),
|
||||||
|
url: unprint.query.url(actorEl, null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const poster = query.img('img.mainThumb');
|
||||||
|
const previewCount = query.number('img.mainThumb', { attribute: 'cnt' });
|
||||||
|
|
||||||
|
if (poster && !placeholder.test(poster)) {
|
||||||
|
const posterFallbacks = [
|
||||||
|
poster.replace('-1x', '-3x'),
|
||||||
|
poster.replace('-1x', '-2x'),
|
||||||
|
poster.replace('-1x', '-4x'),
|
||||||
|
poster,
|
||||||
|
];
|
||||||
|
|
||||||
|
release.poster = posterFallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previewCount) {
|
||||||
|
release.photos = Array.from(
|
||||||
|
{ length: previewCount - 1 },
|
||||||
|
(value, index) => [3, 2, 4, 1].map((scale) => unprint.prefixUrl(query.img('img.mainThumb', { attribute: `src${index + 1}_${scale}x` }))).filter(Boolean), // 4x is unnecessarily big and possibly upscaled
|
||||||
|
).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
release.photoCount = query.number('.timeDate', { match: /(\d+) photos/i, matchIndex: 1 });
|
||||||
|
|
||||||
|
release.entryId = getEntryId(release);
|
||||||
|
|
||||||
|
return release;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatest(channel, page = 1) {
|
||||||
|
const url = `${channel.url}/categories/movies_${page}_d.html`;
|
||||||
|
const res = await unprint.get(url, { selectAll: '.item-video' });
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeAll(res.context, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeScene({ query, html }, { url, entity, baseRelease }) {
|
||||||
|
const release = { url };
|
||||||
|
|
||||||
|
release.title = query.content('.title h2');
|
||||||
|
release.description = query.content('.description p');
|
||||||
|
|
||||||
|
release.date = query.date('.info p', 'MMMM D, YYYY');
|
||||||
|
release.duration = query.duration('.info p');
|
||||||
|
|
||||||
|
release.actors = query.all('.info a[href*="models/"], .info a[href*="sets.php"]').map((actorEl) => ({
|
||||||
|
name: unprint.query.content(actorEl),
|
||||||
|
url: unprint.query.url(actorEl, null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const poster = unprint.prefixUrl(query.img('.update_thumb') || html.match(/poster="(.*\.jpg)"/)?.[1], entity.url);
|
||||||
|
|
||||||
|
if (poster && !placeholder.test(poster)) {
|
||||||
|
const posterFallbacks = [
|
||||||
|
poster.replace('-1x', '-3x'),
|
||||||
|
poster.replace('-1x', '-2x'),
|
||||||
|
poster.replace('-1x', '-4x'),
|
||||||
|
poster,
|
||||||
|
];
|
||||||
|
|
||||||
|
// scene page poster usually different from overview page, don't replace
|
||||||
|
if (baseRelease?.poster && baseRelease.poster !== poster) {
|
||||||
|
release.photos = baseRelease.photos
|
||||||
|
? [posterFallbacks, ...baseRelease.photos]
|
||||||
|
: [posterFallbacks];
|
||||||
|
} else {
|
||||||
|
release.poster = posterFallbacks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const trailer = html.match(/src="(.*\.mp4)"/)?.[1];
|
||||||
|
|
||||||
|
if (trailer) {
|
||||||
|
release.trailer = unprint.prefixUrl(encodeURI(trailer), entity.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
release.tags = query.contents('.info .tags a');
|
||||||
|
|
||||||
|
release.photoCount = query.number('.info', { match: /(\d+) photos/i, matchIndex: 1 });
|
||||||
|
release.entryId = getEntryId(release);
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeMovie({ query, element }, { entity, url }) {
|
||||||
|
const release = { url };
|
||||||
|
|
||||||
|
release.title = query.content('.title h2');
|
||||||
|
release.description = query.content('.aboutArea p');
|
||||||
|
|
||||||
|
release.covers = [[
|
||||||
|
query.img('.update_thumb', { attribute: 'src0_2x', origin: entity.url }),
|
||||||
|
query.img('.update_thumb', { attribute: 'src0_1x', origin: entity.url }),
|
||||||
|
query.img('.update_thumb', { attribute: 'src0', origin: entity.url }),
|
||||||
|
// usually upscaled
|
||||||
|
query.img('.update_thumb', { attribute: 'src0_4x', origin: entity.url }),
|
||||||
|
query.img('.update_thumb', { attribute: 'src0_3x', origin: entity.url }),
|
||||||
|
].filter(Boolean)];
|
||||||
|
|
||||||
|
release.entryId = getEntryId(release);
|
||||||
|
|
||||||
|
release.scenes = scrapeAll(unprint.initAll(element, '.item-video'));
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeProfile({ query, element }, { url, entity }) {
|
||||||
|
const profile = { url };
|
||||||
|
|
||||||
|
const bio = Object.fromEntries(query.all('.stats li')
|
||||||
|
.map((row) => [
|
||||||
|
slugify(unprint.query.content(row, '.data-name, span'), '_'),
|
||||||
|
unprint.query.text(row),
|
||||||
|
])
|
||||||
|
.filter(([key, value]) => key && value));
|
||||||
|
|
||||||
|
profile.description = query.content('.aboutArea p');
|
||||||
|
|
||||||
|
profile.birthPlace = bio.place_of_birth;
|
||||||
|
profile.dateOfBirth = unprint.extractDate(bio.age, 'MMMM D, YYYY');
|
||||||
|
|
||||||
|
profile.height = Number(bio.height?.match(/(\d+)\s*cm/)?.[1]) || (/\d fe*t \d+ inch/i.test(bio.height) && feetInchesToCm(bio.height)) || null;
|
||||||
|
profile.measurements = bio.measurements;
|
||||||
|
|
||||||
|
profile.hairColor = bio.hair_color;
|
||||||
|
profile.eyes = bio.eye_color;
|
||||||
|
|
||||||
|
profile.avatar = [
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_4x', origin: entity.url }),
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_3x', origin: entity.url }),
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_2x', origin: entity.url }),
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0_1x', origin: entity.url }),
|
||||||
|
query.img('.model_bio_thumb', { attribute: 'src0', origin: entity.url }),
|
||||||
|
].filter((avatar) => avatar && !placeholder.test(avatar));
|
||||||
|
|
||||||
|
profile.scenes = scrapeAll(unprint.initAll(element, '.item-video'));
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile({ name: actorName, url: actorUrl }, { entity, include, parameters }) {
|
||||||
|
const res = await [
|
||||||
|
actorUrl,
|
||||||
|
`${entity.url}${parameters.path || ''}/models/${slugify(actorName, '-')}.html`,
|
||||||
|
`${entity.url}${parameters.path || ''}/models/${slugify(actorName, '')}.html`,
|
||||||
|
].reduce(async (chain, url) => {
|
||||||
|
const prevRes = await chain;
|
||||||
|
|
||||||
|
if (prevRes.ok || !url) {
|
||||||
|
return prevRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actorRes = await unprint.get(url);
|
||||||
|
|
||||||
|
if (actorRes.ok) {
|
||||||
|
return {
|
||||||
|
...actorRes,
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return prevRes;
|
||||||
|
}, Promise.resolve({ ok: false, status: null }));
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeProfile(res.context, { entity, include, url: res.url });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchLatest,
|
||||||
|
fetchProfile,
|
||||||
|
scrapeScene,
|
||||||
|
scrapeMovie,
|
||||||
|
};
|
||||||
|
|
@ -60,7 +60,7 @@ function kgToLbs(kgs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function curateConvertInput(string) {
|
function curateConvertInput(string) {
|
||||||
if (/['’]|(fe*t)/.test(string)) {
|
if (/['’]|(fe*o*t)/.test(string)) {
|
||||||
const result = string.match(/(\d+).*(\d+)/);
|
const result = string.match(/(\d+).*(\d+)/);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
@ -74,9 +74,6 @@ function curateConvertInput(string) {
|
||||||
function convertManyApi(input, to) {
|
function convertManyApi(input, to) {
|
||||||
const curatedInput = curateConvertInput(input);
|
const curatedInput = curateConvertInput(input);
|
||||||
|
|
||||||
console.log('CONVERT', input);
|
|
||||||
console.log('RESULT', curatedInput);
|
|
||||||
|
|
||||||
return Math.round(convertMany(curatedInput).to(to)) || null;
|
return Math.round(convertMany(curatedInput).to(to)) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
const unprint = require('unprint');
|
||||||
|
|
||||||
|
async function tryUrls(urls, options) {
|
||||||
|
return urls.filter(Boolean).reduce(async (chain, url) => {
|
||||||
|
const acc = await chain;
|
||||||
|
|
||||||
|
if (acc?.res.ok) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await unprint.get(url, options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
res,
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}, Promise.resolve());
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = tryUrls;
|
||||||
|
|
@ -113,6 +113,10 @@ const actors = [
|
||||||
// perv city
|
// perv city
|
||||||
{ entity: 'pervcity', name: 'Brooklyn Gray', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'eyes', 'hairColor'] },
|
{ entity: 'pervcity', name: 'Brooklyn Gray', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'eyes', 'hairColor'] },
|
||||||
{ entity: 'dpdiva', name: 'Liz Jordan', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'eyes', 'hairColor'] },
|
{ entity: 'dpdiva', name: 'Liz Jordan', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'eyes', 'hairColor'] },
|
||||||
|
// arch angel
|
||||||
|
{ entity: 'archangel', name: 'Summer Brielle', fields: ['avatar', 'description', 'dateOfBirth', 'age', 'measurements', 'height', 'aliases'] },
|
||||||
|
// the flourish
|
||||||
|
{ entity: 'theflourishxxx', name: 'XWifeKaren', fields: ['avatar', 'description'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const actorScrapers = scrapers.actors;
|
const actorScrapers = scrapers.actors;
|
||||||
|
|
@ -194,7 +198,7 @@ async function init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source && source !== entitySlug) {
|
if (source && source !== entitySlug) {
|
||||||
console.log('____', entitySlug);
|
// console.log('____', entitySlug);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue