forked from DebaucheryLibrarian/traxxx
Added ManyVids with fixed channels, WIP.
This commit is contained in:
@@ -95,6 +95,14 @@ const tags = [
|
|||||||
slug: '69',
|
slug: '69',
|
||||||
group: 'position',
|
group: 'position',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'AI generated',
|
||||||
|
slug: 'ai',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'AI enhanced',
|
||||||
|
slug: 'aienhanced',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'airtight',
|
name: 'airtight',
|
||||||
slug: 'airtight',
|
slug: 'airtight',
|
||||||
|
|||||||
@@ -621,6 +621,11 @@ const networks = [
|
|||||||
url: 'https://mamacitaz.com',
|
url: 'https://mamacitaz.com',
|
||||||
parent: 'porndoe',
|
parent: 'porndoe',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'manyvids',
|
||||||
|
name: 'ManyVids',
|
||||||
|
url: 'https://www.manyvids.com',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: 'men',
|
slug: 'men',
|
||||||
name: 'Men',
|
name: 'Men',
|
||||||
|
|||||||
@@ -7691,6 +7691,14 @@ const sites = [
|
|||||||
delete: true,
|
delete: true,
|
||||||
parent: 'mamacitaz',
|
parent: 'mamacitaz',
|
||||||
},
|
},
|
||||||
|
// MANYVIDS
|
||||||
|
{
|
||||||
|
name: 'nicoledoshi',
|
||||||
|
slug: 'mv.nicoledoshi',
|
||||||
|
url: 'https://nicoledoshi.manyvids.com',
|
||||||
|
hasLogo: false,
|
||||||
|
parent: 'manyvids',
|
||||||
|
},
|
||||||
// MARISKA X
|
// MARISKA X
|
||||||
{
|
{
|
||||||
name: 'MariskaX',
|
name: 'MariskaX',
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ function curateActor(actor, withDetails = false, isProfile = false) {
|
|||||||
name: actor.name,
|
name: actor.name,
|
||||||
slug: actor.slug,
|
slug: actor.slug,
|
||||||
url: actor.url,
|
url: actor.url,
|
||||||
|
entryId: actor.entry_id,
|
||||||
gender: actor.gender,
|
gender: actor.gender,
|
||||||
orientation: actor.orientation,
|
orientation: actor.orientation,
|
||||||
entityId: actor.entity_id,
|
entityId: actor.entity_id,
|
||||||
@@ -693,7 +694,7 @@ async function scrapeProfiles(actor, sources, entitiesBySlug, existingProfilesBy
|
|||||||
}
|
}
|
||||||
}), Promise.reject(new Error()));
|
}), Promise.reject(new Error()));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
|
|
||||||
if (error.code !== 'PROFILE_NOT_AVAILABLE') {
|
if (error.code !== 'PROFILE_NOT_AVAILABLE') {
|
||||||
logger.error(`Failed to fetch profile for '${actor.name}': ${error.message}`);
|
logger.error(`Failed to fetch profile for '${actor.name}': ${error.message}`);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const pornbox = require('./pornbox');
|
|||||||
const littlecapricedreams = require('./littlecapricedreams');
|
const littlecapricedreams = require('./littlecapricedreams');
|
||||||
const loveherfilms = require('./loveherfilms');
|
const loveherfilms = require('./loveherfilms');
|
||||||
const bluedonkeymedia = require('./bluedonkeymedia');
|
const bluedonkeymedia = require('./bluedonkeymedia');
|
||||||
|
const manyvids = require('./manyvids');
|
||||||
const mikeadriano = require('./mikeadriano');
|
const mikeadriano = require('./mikeadriano');
|
||||||
const aylo = require('./aylo');
|
const aylo = require('./aylo');
|
||||||
const missax = require('./missax');
|
const missax = require('./missax');
|
||||||
@@ -206,14 +207,6 @@ module.exports = {
|
|||||||
jerkaoke: modelmedia,
|
jerkaoke: modelmedia,
|
||||||
modelmediaasia: modelmedia,
|
modelmediaasia: modelmedia,
|
||||||
delphine: modelmedia,
|
delphine: modelmedia,
|
||||||
// etc
|
|
||||||
'18vr': badoink,
|
|
||||||
theflourishxxx: theflourish,
|
|
||||||
pierrewoodman,
|
|
||||||
exploitedx, // only from known URL that will specify site
|
|
||||||
fullpornnetwork,
|
|
||||||
adultempire,
|
|
||||||
allherluv: missax,
|
|
||||||
// americanpornstar,
|
// americanpornstar,
|
||||||
angelogodshackoriginal,
|
angelogodshackoriginal,
|
||||||
babevr: badoink,
|
babevr: badoink,
|
||||||
@@ -283,4 +276,13 @@ module.exports = {
|
|||||||
slayed: vixen,
|
slayed: vixen,
|
||||||
wifey: vixen,
|
wifey: vixen,
|
||||||
vrcosplayx: badoink,
|
vrcosplayx: badoink,
|
||||||
|
// etc
|
||||||
|
'18vr': badoink,
|
||||||
|
theflourishxxx: theflourish,
|
||||||
|
pierrewoodman,
|
||||||
|
exploitedx, // only from known URL that will specify site
|
||||||
|
fullpornnetwork,
|
||||||
|
adultempire,
|
||||||
|
allherluv: missax,
|
||||||
|
manyvids,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -206,12 +206,13 @@ function scrapeProfile(payload, actorName, channel, url) {
|
|||||||
|
|
||||||
profile.entryId = actorData?.id;
|
profile.entryId = actorData?.id;
|
||||||
|
|
||||||
profile.birthplace = getBioItem(bioData, 'born');
|
profile.birthPlace = getBioItem(bioData, 'born');
|
||||||
|
|
||||||
profile.dateOfBirth = getBioItem(bioData, 'birthdate');
|
profile.dateOfBirth = getBioItem(bioData, 'birthdate');
|
||||||
profile.age = getBioItem(bioData, 'age');
|
profile.age = getBioItem(bioData, 'age');
|
||||||
|
|
||||||
profile.eyes = getBioItem(bioData, 'eyes');
|
profile.eyes = getBioItem(bioData, 'eyes');
|
||||||
|
profile.hairColor = getBioItem(bioData, 'hair');
|
||||||
profile.weight = convert(getBioItem(bioData, 'weight'), 'lb', 'kg');
|
profile.weight = convert(getBioItem(bioData, 'weight'), 'lb', 'kg');
|
||||||
profile.height = convert(getBioItem(bioData, 'height'), 'cm');
|
profile.height = convert(getBioItem(bioData, 'height'), 'cm');
|
||||||
profile.measurements = getBioItem(bioData, 'measurements');
|
profile.measurements = getBioItem(bioData, 'measurements');
|
||||||
|
|||||||
213
src/scrapers/manyvids.js
Executable file
213
src/scrapers/manyvids.js
Executable file
@@ -0,0 +1,213 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const unprint = require('unprint');
|
||||||
|
|
||||||
|
const slugify = require('../utils/slugify');
|
||||||
|
|
||||||
|
function splitTag(tag) {
|
||||||
|
return tag
|
||||||
|
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||||
|
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // handle sequence of caps
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTrailer(videoId) {
|
||||||
|
if (!videoId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await unprint.get(`https://api.manyvids.com/store/video/${videoId}/private`);
|
||||||
|
|
||||||
|
if (res.ok && res.data?.statusCode === 200) {
|
||||||
|
return res.data.data.teaser.filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModelUrl(modelData) {
|
||||||
|
const slug = modelData.slug || modelData.profileUrl?.match(/\/profile\/\d+\/(\w+)/i)?.[1] || null;
|
||||||
|
|
||||||
|
if (slug) {
|
||||||
|
return `https://${slug}.manyvids.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrapeScene(data, context) {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.url = data.url
|
||||||
|
? `https://www.manyvids.com${data.url}`
|
||||||
|
: `https://www.manyvids.com/Video/${data.id}/${data.slug || ''}`;
|
||||||
|
|
||||||
|
release.entryId = data.id;
|
||||||
|
|
||||||
|
release.attributes = {
|
||||||
|
price: data.price?.regular,
|
||||||
|
discount: data.price?.discountedPrice,
|
||||||
|
isCustomOrder: data.isCustomOrder,
|
||||||
|
isPremium: data.isPremium,
|
||||||
|
isPrivate: data.isPrivate,
|
||||||
|
isExclusive: data.isExclusive,
|
||||||
|
isStreamOnly: data.isStreamOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
release.title = data.title;
|
||||||
|
release.description = data.description;
|
||||||
|
|
||||||
|
release.date = new Date(data.launchDate);
|
||||||
|
release.duration = unprint.extractDuration(data.duration || data.videoDuration);
|
||||||
|
|
||||||
|
const model = data.creator || data.model;
|
||||||
|
|
||||||
|
release.actors = [{
|
||||||
|
name: model.stageName || model.displayName,
|
||||||
|
entryId: model.id,
|
||||||
|
avatar: model.avatar?.url || model.avatar,
|
||||||
|
url: getModelUrl(model),
|
||||||
|
}];
|
||||||
|
|
||||||
|
release.tags = [
|
||||||
|
data.is3D && '3d',
|
||||||
|
data.isAiGenerated && 'ai',
|
||||||
|
...data.tagList?.map((tag) => splitTag(tag.label)) || [],
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
release.poster = data.thumbnail?.url || data.screenshot || data.thumbnail; // thumbnail URL is full size, thumbnail direct is thumb sized
|
||||||
|
|
||||||
|
if (data.preview?.url) {
|
||||||
|
release.trailer = data.preview?.url;
|
||||||
|
} else if (context.includeTrailers) {
|
||||||
|
release.trailer = await getTrailer(data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
release.qualities = [data.height].filter(Boolean);
|
||||||
|
|
||||||
|
console.log(release);
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatest(channel, page = 1) {
|
||||||
|
const idRes = await unprint.request(channel.url, {
|
||||||
|
method: 'head',
|
||||||
|
followRedirects: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const profileId = idRes.headers.get('location')?.match(/\/profile\/(\d+)/i)?.[1];
|
||||||
|
|
||||||
|
if (profileId) {
|
||||||
|
const res = await unprint.get(`https://api.manyvids.com/store/videos/${profileId}?sort=newest&limit=100&page=${page}`);
|
||||||
|
|
||||||
|
if (res.ok && res.data?.statusCode === 200) {
|
||||||
|
return Promise.all(res.data.data.map(async (data) => scrapeScene(data, channel)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchScene(url, channel, baseRelease, context) {
|
||||||
|
const videoId = new URL(url).pathname.match(/\/video\/(\d+)/i)?.[1];
|
||||||
|
|
||||||
|
if (!videoId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await unprint.get(`https://api.manyvids.com/store/video/${videoId}`);
|
||||||
|
|
||||||
|
if (res.ok && res.data?.statusCode === 200) {
|
||||||
|
return scrapeScene(res.data.data, {
|
||||||
|
channel,
|
||||||
|
includeTrailers: context.includeTrailers,
|
||||||
|
isDeep: !!baseRelease.entryId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeProfile(data) {
|
||||||
|
const profile = {};
|
||||||
|
|
||||||
|
profile.description = [data.bio, data.description].filter(Boolean).join(' | ') || null;
|
||||||
|
|
||||||
|
profile.placeOfResidence = data.location || null;
|
||||||
|
profile.ethnicity = data.ethnicity || null;
|
||||||
|
profile.hairColor = data.hairColor || null;
|
||||||
|
|
||||||
|
profile.gender = data.identification?.toLowerCase() || null;
|
||||||
|
profile.orientation = data.orientation;
|
||||||
|
|
||||||
|
profile.avatar = data.avatar || null;
|
||||||
|
profile.photos = [data.portrait].filter(Boolean);
|
||||||
|
|
||||||
|
profile.socials = [data.socLinkTwitter, data.socLinkInstagram, data.socLinkReddit].filter(Boolean);
|
||||||
|
|
||||||
|
const dateOfBirth = data.dob && unprint.extractDate(data.dob);
|
||||||
|
|
||||||
|
if (dateOfBirth && new Date().getFullYear() - dateOfBirth.getFullYear() > 18) {
|
||||||
|
// date of births that would not be legal are likely bogus
|
||||||
|
profile.dateOfBirth = dateOfBirth;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(profile);
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getActorUrl(actor) {
|
||||||
|
if (actor.url) {
|
||||||
|
const idRes = await unprint.request(actor.url, {
|
||||||
|
method: 'head',
|
||||||
|
followRedirects: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const profileId = idRes.headers.get('location')?.match(/\/profile\/(\d+)/i)?.[1];
|
||||||
|
|
||||||
|
if (profileId) {
|
||||||
|
return `https://api.manyvids.com/profile/profile/${profileId}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await unprint.get(`https://api.manyvids.com/search/autocomplete?keywords=${slugify(actor.name, '+')}`);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const model = res.data?.models?.find((modelResult) => modelResult.stage_name === actor.name || modelResult.username === actor.name);
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
const profileId = model.url?.match(/\/profile\/(\d+)/i)?.[1];
|
||||||
|
|
||||||
|
if (profileId) {
|
||||||
|
return `https://api.manyvids.com/profile/profile/${profileId}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile(actor, entity) {
|
||||||
|
const actorUrl = await getActorUrl(actor);
|
||||||
|
|
||||||
|
if (actorUrl) {
|
||||||
|
const res = await unprint.get(actorUrl);
|
||||||
|
|
||||||
|
if (res.ok && res.data?.username) {
|
||||||
|
return scrapeProfile(res.data, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actorUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchLatest,
|
||||||
|
fetchProfile,
|
||||||
|
fetchScene,
|
||||||
|
};
|
||||||
@@ -44,6 +44,7 @@ const pornbox = require('./pornbox');
|
|||||||
const littlecapricedreams = require('./littlecapricedreams');
|
const littlecapricedreams = require('./littlecapricedreams');
|
||||||
const loveherfilms = require('./loveherfilms');
|
const loveherfilms = require('./loveherfilms');
|
||||||
const bluedonkeymedia = require('./bluedonkeymedia');
|
const bluedonkeymedia = require('./bluedonkeymedia');
|
||||||
|
const manyvids = require('./manyvids');
|
||||||
const mikeadriano = require('./mikeadriano');
|
const mikeadriano = require('./mikeadriano');
|
||||||
const aylo = require('./aylo');
|
const aylo = require('./aylo');
|
||||||
const missax = require('./missax');
|
const missax = require('./missax');
|
||||||
@@ -150,6 +151,7 @@ module.exports = {
|
|||||||
littlecapricedreams,
|
littlecapricedreams,
|
||||||
loveherfilms,
|
loveherfilms,
|
||||||
mamacitaz: porndoe,
|
mamacitaz: porndoe,
|
||||||
|
manyvids,
|
||||||
mariskax,
|
mariskax,
|
||||||
mikeadriano,
|
mikeadriano,
|
||||||
missax,
|
missax,
|
||||||
|
|||||||
@@ -265,6 +265,7 @@ const actors = [
|
|||||||
{ entity: 'tokyohot', name: 'Mai Kawana', url: 'https://my.tokyo-hot.com/cast/2099/', fields: ['avatar', 'birthPlace', 'height', 'cup', 'bust', 'waist', 'hip', 'hairStyle', 'shoeSize', 'bloodType'] },
|
{ entity: 'tokyohot', name: 'Mai Kawana', url: 'https://my.tokyo-hot.com/cast/2099/', fields: ['avatar', 'birthPlace', 'height', 'cup', 'bust', 'waist', 'hip', 'hairStyle', 'shoeSize', 'bloodType'] },
|
||||||
{ entity: 'wakeupnfuck', name: 'Abby Lee Brazil', fields: ['avatar', 'nationality'] },
|
{ entity: 'wakeupnfuck', name: 'Abby Lee Brazil', fields: ['avatar', 'nationality'] },
|
||||||
{ entity: 'darkkotv', name: 'Aidra Fox', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'measurements', 'naturalBoobs', 'hasTattoos', 'hasPiercings'] },
|
{ entity: 'darkkotv', name: 'Aidra Fox', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'measurements', 'naturalBoobs', 'hasTattoos', 'hasPiercings'] },
|
||||||
|
{ entity: 'jaxslayher', name: 'Eliza Ibarra', fields: ['avatar', 'description', 'eyes', 'height', 'weight', 'birthPlace', 'measurements', 'hairColor'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const actorScrapers = scrapers.actors;
|
const actorScrapers = scrapers.actors;
|
||||||
|
|||||||
Reference in New Issue
Block a user