Added Radical metadata layout scraper and the Got Filled and Inserted channels.
|
@ -306,6 +306,10 @@ module.exports = {
|
|||
interval: 1000,
|
||||
concurrency: 1,
|
||||
},
|
||||
'www.twistys.com': {
|
||||
interval: 1000,
|
||||
concurrency: 1,
|
||||
},
|
||||
'westcoastproductions.com': {
|
||||
interval: 100,
|
||||
concurrency: 1,
|
||||
|
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 377 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 377 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 44 KiB |
|
@ -7554,6 +7554,26 @@ const sites = [
|
|||
independent: true,
|
||||
parent: 'radical',
|
||||
},
|
||||
{
|
||||
name: 'Got Filled',
|
||||
slug: 'gotfilled',
|
||||
url: 'https://gotfilled.com',
|
||||
independent: true,
|
||||
parent: 'radical',
|
||||
parameters: {
|
||||
layout: 'metadata',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Inserted',
|
||||
slug: 'inserted',
|
||||
url: 'https://inserted.com',
|
||||
independent: true,
|
||||
parent: 'radical',
|
||||
parameters: {
|
||||
layout: 'metadata',
|
||||
},
|
||||
},
|
||||
// REALITY KINGS
|
||||
{
|
||||
name: 'Look At Her Now',
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
'use strict';
|
||||
|
||||
const http = require('../utils/http');
|
||||
const qu = require('../utils/qu');
|
||||
const slugify = require('../utils/slugify');
|
||||
const { lbsToKg, feetInchesToCm } = require('../utils/convert');
|
||||
|
||||
function scrapeSceneMetadata(data, channel) {
|
||||
const release = {};
|
||||
|
||||
release.entryId = data.id;
|
||||
release.url = `${channel.url}/tour/videos/${data.id}/${slugify(data.title, '-', { removePunctuation: true })}`;
|
||||
|
||||
release.title = data.title;
|
||||
release.description = data.description;
|
||||
|
||||
release.date = new Date(data.release_date);
|
||||
release.duration = qu.durationToSeconds(data.videos_duration);
|
||||
|
||||
release.actors = data.models.map((model) => ({
|
||||
entryId: model.id,
|
||||
name: model.name,
|
||||
gender: model.gender,
|
||||
avatar: model.thumb,
|
||||
url: `${channel.url}/tour/models/${model.id}/${slugify(model.name, '-', { removePunctuation: true })}`,
|
||||
}));
|
||||
|
||||
release.poster = data.trailer?.poster || [data.thumb?.replace('mobile.jpg', '.jpg'), data.thumb];
|
||||
release.photos = [
|
||||
data.extra_thumbs?.find((url) => /portrait1.jpg/.test(url)),
|
||||
data.extra_thumbs?.find((url) => /scene.jpg/.test(url)),
|
||||
data.extra_thumbs?.find((url) => /portrait2.jpg/.test(url)),
|
||||
]; // ordered by chronology: portrait1.jpg and scene.jpg are usually pre-shoot poses, portrait2.jpg is the cumshot aftermath
|
||||
|
||||
release.trailer = data.trailer && {
|
||||
src: data.trailer.src,
|
||||
type: data.trailer.type,
|
||||
};
|
||||
|
||||
release.teaser = data.special_thumbs;
|
||||
|
||||
release.tags = [].concat(data.tags?.map((tag) => tag.name));
|
||||
release.qualities = data.downloads && Object.values(data.downloads)?.map((download) => download.meta_data.height);
|
||||
release.stars = data.rating;
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
function scrapeAllMetadata(scenes, channel) {
|
||||
return scenes.map((data) => scrapeSceneMetadata(data, channel));
|
||||
}
|
||||
|
||||
function scrapeProfileMetadata(data, channel) {
|
||||
const profile = {};
|
||||
|
||||
profile.entryId = data.id;
|
||||
profile.url = `${channel.url}/tour/models/${data.id}/${slugify(data.name, '-', { removePunctuation: true })}`;
|
||||
|
||||
profile.description = data.attributes.bio?.value;
|
||||
profile.dateOfBirth = qu.parseDate(data.attributes.birthdate?.value, 'YYYY-MM-DD');
|
||||
profile.gender = data.gender;
|
||||
profile.age = data.attributes.age?.value;
|
||||
profile.birthPlace = data.attributes.born?.value;
|
||||
|
||||
profile.measurements = data.attributes.measurements?.value;
|
||||
profile.height = feetInchesToCm(data.attributes.height?.value);
|
||||
profile.weight = lbsToKg(data.attributes.weight?.value);
|
||||
|
||||
profile.eyes = data.attributes.eyes?.value;
|
||||
profile.hairColor = data.attributes.hair?.value;
|
||||
|
||||
profile.avatar = data.thumb;
|
||||
profile.date = new Date(data.publish_date);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
async function fetchLatestMetadata(channel, page = 1) {
|
||||
const url = `${channel.url}/tour/videos?page=${page}`;
|
||||
const res = await http.get(url, {
|
||||
parse: true,
|
||||
extract: {
|
||||
runScripts: 'dangerously',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.ok && res.window.__DATA__) {
|
||||
return scrapeAllMetadata(res.window.__DATA__.videos.items, channel);
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
return res.window.__DATA__?.error || null;
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchSceneMetadata(url, channel) {
|
||||
const res = await http.get(url, {
|
||||
parse: true,
|
||||
extract: {
|
||||
runScripts: 'dangerously',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.ok && res.window.__DATA__?.video) {
|
||||
return scrapeSceneMetadata(res.window.__DATA__.video, channel);
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
return res.window.__DATA__?.error || null;
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchProfileMetadata(actor, channel) {
|
||||
const res = await http.get(`${channel.url}/tour/search-preview/${actor.name}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const model = res.body.models?.items.find((modelX) => slugify(modelX.name) === actor.slug);
|
||||
|
||||
if (model) {
|
||||
return scrapeProfileMetadata(model, channel);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
metadata: {
|
||||
fetchLatest: fetchLatestMetadata,
|
||||
fetchScene: fetchSceneMetadata,
|
||||
fetchProfile: fetchProfileMetadata,
|
||||
},
|
||||
};
|
|
@ -52,6 +52,7 @@ const pierrewoodman = require('./pierrewoodman');
|
|||
const pinkyxxx = require('./pinkyxxx');
|
||||
const privateNetwork = require('./private'); // reserved keyword
|
||||
const purgatoryx = require('./purgatoryx'); // reserved keyword
|
||||
const radical = require('./radical');
|
||||
const score = require('./score');
|
||||
const spizoo = require('./spizoo');
|
||||
const teamskeet = require('./teamskeet');
|
||||
|
@ -138,6 +139,7 @@ const scrapers = {
|
|||
pornpros: whalemember,
|
||||
private: privateNetwork,
|
||||
purgatoryx,
|
||||
radical,
|
||||
score,
|
||||
sexyhub: mindgeek,
|
||||
spizoo,
|
||||
|
@ -208,6 +210,7 @@ const scrapers = {
|
|||
gaywire: bangbros,
|
||||
girlfaction: fullpornnetwork,
|
||||
gloryholesecrets: aziani,
|
||||
gotfilled: radical,
|
||||
hergape: fullpornnetwork,
|
||||
hitzefrei,
|
||||
homemadeanalwhores: fullpornnetwork,
|
||||
|
@ -216,6 +219,7 @@ const scrapers = {
|
|||
hushpass: hush,
|
||||
hussiepass: hush,
|
||||
iconmale: mindgeek,
|
||||
inserted: radical,
|
||||
interracialpass: hush,
|
||||
interracialpovs: hush,
|
||||
inthecrack,
|
||||
|
|